vuejs / vitepress

Vite & Vue powered static site generator.
https://vitepress.dev
MIT License
12.46k stars 2.03k forks source link

Runtime dynamic routes #2892

Open nhynes opened 1 year ago

nhynes commented 1 year ago

Is your feature request related to a problem? Please describe.

I'm using Vitepress to generate a combined static site that includes a substantial dynamic component, which ideally would include dynamic routing based on stuff like user ids–standard SPA stuff. I am using Vitepress in this way because it better for SEO than a Vue SPA, and is generally easier for development than using two separate builds.

Currently, I have /index.md as static, and app/index.md as a stub containing a client-only component. I want app/[page].md (or something) to be dynamic and render another client-only component, but this is (mostly) not possible even with dynamic routing, as it is file-based.

Describe the solution you'd like

It would be great if there were a [page].vue or other config option that marked a route as client-side only such that it has the same behavior as normal vue-router. Essentially a way to have a partially-static Vue site.

Describe alternatives you've considered

It is possible to modify the layout's 404 handler such that it renders the component for the runtime dynamic route. The browser will report a 404, which is mostly a cosmetic issue. Otherwise, this workaround complicates the split between application and view logic, which is not ideal.

The workaround looks a little like this plus a v-if in the template

const pageId = computed(() => {
  const relPath = page.value.relativePath || window.location.pathname;
  return relPath.match(/\/app\/([\w\-]+)\/?$/)?.[1] ?? undefined;
});

Additional context

No response

Validations

edualb commented 9 months ago

Thanks for your workaround, I think it will work (not tested yet). Anyway, I would like to have some kind of solution to route to a vue component too.

I was investigate in the source code and I think we could overwrite the function onAfterRouteChanged: https://github.com/vuejs/vitepress/blob/2d96200a21f07c45d3c68d7d25ac9e032b39c68f/src/client/app/router.ts#L35

The function above is executed in the go function of the router: https://github.com/vuejs/vitepress/blob/2d96200a21f07c45d3c68d7d25ac9e032b39c68f/src/client/app/router.ts#L66-L71

From that, we could define the onAfterRouteChanged function as follows (not tested yet):

// .vitepress/theme/index.js
import './custom.css'
import * as VueRouter from 'vue-router'
import Layout from '../Layout.vue'
import MyComponent from '../MyComponent.vue'

export default {
    Layout,
    enhanceApp({ app, router }) {
        const routes = [
             { path: '/custom', component: MyComponent },
        ]
        const myRouter = VueRouter.createRouter({
             history: VueRouter.createWebHashHistory(),
             routes, // short for `routes: routes`
        })
        app.use(myRouter)
        router.onAfterRouteChanged = (to: string) => {
            // redirect to the route you want (i.e. /custom)
        }
    }
}

I think it is another workaround and I am not sure if it can work... Someone who has more experience could help us with that or maybe have an idea what we need to implement this enhancement inside vitepress.

edualb commented 9 months ago

Hello all, I found another workaround (extending the default theme). It is similar with my previous post:

  1. Create a .vue file:
    // Example.vue
    <template>
    <p class="text-4xl font-sans font-bold">Hello World!!!!</p>
    </template>
  2. Edit your .vitepress/theme/index.js:
    
    import DefaultTheme from 'vitepress/theme'
    import { markRaw } from 'vue'
    import './custom.css'
    import Layout from '../Layout.vue'
    import Example from '../Example.vue'

export default { Layout, enhanceApp({ app, router, siteData }) { router.onBeforePageLoad = async (to) => { // Here you can set the routes you want to configure. if (to == '/example' || to == '/example.html') { router.route.path = to router.route.component = markRaw(Example) // Here you set your .vue file router.route.data = { frontmatter: { sidebar: false, layout: 'page' }, }; return false } return true } } }



This is possible because the function `onBeforePageLoad` is called before loading the `.md` pages:
https://github.com/vuejs/vitepress/blob/9fed2d2b8cb146b3798e4262114d34e5c9d2a257/src/client/app/router.ts#L77

@brc-dd Do you think this is a good solution and we should document it or do you suggest another approach?
hhstore commented 3 months ago

@edualb