Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pre-rendered dynamic routes (static host deployment) #1476

Open
brillout opened this issue Feb 6, 2024 · 28 comments
Open

Pre-rendered dynamic routes (static host deployment) #1476

brillout opened this issue Feb 6, 2024 · 28 comments

Comments

@brillout
Copy link
Member

brillout commented Feb 6, 2024

Currently, as explained at https://vike.dev/pre-rendering, parameterized routes (e.g. /movie/@movieId) need to use onBeforePrerenderStart() to be able to be pre-rendered.

Instead, it would be great if Vike supports resolving the route dynamically at runtime on the client-side. (E.g. /todos/@todoId which is inherently dynamic, since new To-Do items can be added at any time.)

In the meantime, as a workaround, the user can use React Router / Vue Router / Solid Router (in addition to using Vike's router) for such dynamic routes.

Learn more

I want to handle routes like /todos/@todoId. I’m looking to keep absolutely everything client side but still have the dynamic routes.

Quoting a user who wants to deploy to a static host.

Basically: instead of one HTML file per URL, having one HTML file for multiple URLs while Vike's client-side takes care of the routing.

// vike.config.js

export default {
  prerender: {
    // Like SPA: all parameterized (i.e. dynamic) routes fallback to / (i.e. index.html)
    fallback: '/'
  }
}
// /pages/admin-panel/+config.js

export default {
  prerender: {
    // All parameterized (i.e. dynamic) routes inside /pages/admin-panel/ fallback to /admin-panel
    fallback: '/admin-panel'
  }
}
// /pages/todos/+config.js

export default {
  prerender: {
    // All parameterized (i.e. dynamic) routes inside /pages/todos/ fallback to /todos
    fallback: '/todos'
  }
}

Conversation & design how we can support that: #1475.

How to set up URL rewrites:

You normally handle this behavior of always serving index.html with a _rewrites file on Netlify.
Is _rewrites somewhat standard?
they all have their own methods
from some googling, Vercel will set the rewrites automatically if you select React as the framework, or you can set manually I think like this

// vercel.json
{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}

In Render you can set the rewrites using their GUI, in a similar vein cloudflare does this automatically as well

@nitedani
Copy link
Member

Hi yall, I'm looking into using vike with react, and I was wondering if this use case was possible... I want to have some pages as SPA and others as SSG, and deploy to static hosting like Netlify.
Now this is in general very simple, I just prerender every page, including all SPA pages. But here's the twist, would it be possible to generate only one index file for all the SPA pages and use a Netlify config to redirect all of those pages to that file? This is exactly what you'd do with a normal Vite setup, but I just want to add some SSG files to the mix

@brillout
Copy link
Member Author

brillout commented Feb 16, 2024

I'm de-prioritizing this. While I personally think that this is a great feature, I still think the other v1-release tickets to be more important. That said, I'm happy to revisit prioritization if it's a blocker for someone.

I wonder: is there any SSR/SSG framework out there that supports this? (If yes, then this also increases the priority.)

@andriygm
Copy link

this would be huge -- I know at least solid-router is able to do this. currently cannot use Vike on one of my Solid projects because of this.

@brillout brillout changed the title Support deploying dynamic route to static host Support dynamic routes for pre-rendered apps (static host deployment) Feb 24, 2024
@brillout
Copy link
Member Author

this would be huge

Agreed.

solid-router is able to do this

That isn't an SSR/SSG framework. It's definitely possible to implement, but it isn't trivial either. Thus I'm genuinely curious if there is an SSR/SSG framework out there that supports this.

currently cannot use Vike on one of my Solid projects because of this

I've added a workaround to my original post. Would that work for you?

@brillout brillout changed the title Support dynamic routes for pre-rendered apps (static host deployment) Pre-rendered dynamic routes (static host deployment) Feb 24, 2024
@andriygm
Copy link

perf, I'll head that route (get it) until Vike's router gets support for this 👌

@CookedApps
Copy link

We'd appreciate this feature as well. 🙌

Is there an example of how to set up the workaround with react-router?

@ConnorLanglois
Copy link

ConnorLanglois commented Mar 18, 2024

That isn't an SSR/SSG framework. It's definitely possible to implement, but it isn't trivial either. Thus I'm genuinely curious if there is an SSR/SSG framework out there that supports this.

If I understand this issue correctly, I believe NextJS supports this kind of routing. Note the new app router in nextjs does not yet support this, but they are working on it. Notably, the "old" pages router supports this as other users mention; although I haven't used that, I'm guessing they're referring to the use of code like this from this page:

For example, a blog could include the following route pages/blog/[slug].js where [slug] is the Dynamic Segment for blog posts.

const router = useRouter()
return <p>Post: {router.query.slug}</p>

It works because I think nextjs allows the parameterized route without requiring you to specify the build-time static urls for it. I also just noticed another user in that issue thread said that Gatsby also supports dynamic routers in static pages.

@brillout
Copy link
Member Author

Hm, I'm not sure. From quickly glazing over the links you posted, it still doesn't seem to me that Next.js supports this (both App Router and Pages Router).

An easy way to test is this: can you create a Next.js app that defines a parameterized route but outputs only one .html file (for multiple URLs)?

Gatsby allows you to use dynamic routers in static exports without configuring server-side redirects.

Vike can do that as well. The question is whether you always need to provide a list of URLs for parameterized routes.

Same test: can you create a Gatsby app that defines a parameterized route but outputs only one .html file (for multiple URLs)?

@ConnorLanglois
Copy link

ConnorLanglois commented Mar 18, 2024

An easy way to test is this: can you create a Next.js app that defines a parameterized route but outputs only one .html file (for multiple URLs)?

Yes I believe so. Here is a test repo that demonstrates this (you can look at the pages/product folder for the dynamic route, the other stuff is nextjs scaffolding defaults etc). When you build it, it will output a single html file for the parameterized route ([id].html) and the routing is done client side. Notably, you don't have to specify any build-time static urls for that particular dynamic route, it just works client side for any param you enter. Let me know if this differs from what you were asking, seems like "dynamic routes" is an overloaded term so trying to make sure we match up on what we mean.

For Gatbsy, I haven't used it before so not sure.

Edit: Actually, now I am seeing that if I manually go to the dynamic route (/product/543875) through the browser address bar it doesn't actually work, it 404s obviously since that file doesn't exist on disk. But if you are already on the index page and click the Link to the dynamic route, it does work. I think this is just due to http-server not rewriting urls, and if I deploy to cloudflare pages they understand nextjs's build output and just rewrite it. So perhaps this clashes with the behavior you are discussing above.

@andriygm
Copy link

andriygm commented Mar 19, 2024

But if you are already on the index page and click the Link to the dynamic route, it does work.

If you reroute a request from say /product/54387 to [id].html, does [id].html route on load? or is client routing only done on navigation from within?

@ConnorLanglois
Copy link

ConnorLanglois commented Mar 19, 2024

Hello, by reroute do you mean if I go to /product/54387 directly in the address bar and have the server rewrite it to the [id].html file does it load that [id].html file? The dummy http-server I used to test I don't think has url rewriting but cloudflare and others do or your own express server can

@andriygm
Copy link

andriygm commented Mar 19, 2024

but cloudflare and others do or your own express server can

Sure, but the question here (I think) is whether or not the pre-rendered dynamic route page (in this case [id].html) routes to say /product/54387 when you serve [id].html with /product/54387 in the URI bar.

@ConnorLanglois
Copy link

ConnorLanglois commented Mar 19, 2024

Yeah it will still show /product/54387 in the address bar when cloudflare serves [id].html. This express example shows this by using sendFile, specifically:

app.get('/blog/:blogID', sendFile('blog/[blogID].html'));

@brillout
Copy link
Member Author

I'm labeling this ticket as high-prio and v1-release. The fact that Next.js's Pages Router (and soon also its App Router) support this makes this a Vike-specific blocker.

Thanks @ConnorLanglois for the example.

@brillout
Copy link
Member Author

As always: feel free to add a comment here if this a blocker specifically for your app.

@Taujor
Copy link

Taujor commented Apr 17, 2024

Personally I have switched from vike to solidjs (which is a bit overkill for my project) due to this feature lacking. I would really love to see this come to vike.

@Trumspringa
Copy link

I think this could be what I've been looking for. I have this with my Gatsby JS app where the site is mostly SSG and then had a dynamic section with user authentication and interaction with Supabase.

They call it "client-only routes" . All of the dynamic pages in my app were at /app/* where essentially the app route becomes an SPA with its own router. I've been waiting to see if it will be possible to do this with solid-start when it hit 1.0 but if Vike can do it I would definitely be interested.

@TimJohns
Copy link

I'm not sure if the following is a GOOD approach, but it's working for us so far, so wanted to share it, and also ask whether it's supported or not. We use Vue, Vike, and vike-vue. The site is hosted as a static site from a Google Cloud bucket, uses SSG pages in a hybrid (SPA-like) fashion, and gets all runtime data from a separate API.

We want both client-side routing and deep linking, while still hosting from a static site. For instance, users will often want to deep link to their custom road trips (e.g. https://epicroadtripplanner.com/roadtrips/d9cc2d68-09d0-4d9e-bd0a-258936c2be65), but the fetched content (and the route itself) for roadtrips are dynamic.

We use file system routing in this project, and all of the following are in the file system at /src/pages/roadtrips/@roadtripId:

"+onBeforePrerenderStart.ts" pre-renders an "all" page:

export async function onBeforePrerenderStart() {
  return ["/roadtrips/all"]
}

Importantly, we use Google Cloud Load Balancer's url map functionality to re-write the incoming request urls, so that it serves this 'all/index.html' page for any requests to /roadtrips/{roadtripid=*}, regardless of the value of roadtripId in the actual request:

pathMatchers:
- defaultService: <redacted>
  name: static
  routeRules:
  - matchRules:
    - pathTemplateMatch: /roadtrips/{roadtripid=*}
    priority: 2100
    routeAction:
      urlRewrite:
        pathTemplateRewrite: /roadtrips/all/index.html

Here's the part I'm not sure about - specifically, whether is supported/documented to use resolveRoute, especially in the data hook, outside of a route function:

In the client-only data hook "+data.client.ts", "pageContext.routeParams" still returns "all" for pageContext.routeParams["roadtripId"], so instead we use resolveRoute there, to (re)parse urlPathname:

export async function data(pageContext: PageContext) {
  const { routeParams } = resolveRoute('/roadtrips/@roadtripId', pageContext.urlPathname);
  const roadtripId = routeParams["roadtripId"];

// <snip> Call a separate backend API w/roadtripId, to get the user's roadtrip data, and return the roadtrip so that it is rendered in +Page.vue
...

@brillout
Copy link
Member Author

@TimJohns Yes LGTM, I don't see any issues with your appraoch. As for pageContext.routeParams it's because it comes from the "server-side" (which is pre-rendered thus resolved to all) — what you're doing makes sense. Btw. would your company be up for sponsoring? It would be a lovely excuse for implementing this feature before the highest-priority tickets 🙂

@niutech
Copy link

niutech commented Jul 2, 2024

There is also another approach to dynamic URLs, at least on Github Pages: using 404.html redirection. When users go to e.g. /todos/{todoID}, the HTML page is not found, so the 404.html is loaded instead with the following <script>:

var todoMatch = location.href.match(/todos\/(.+)$/;
if(todoMatch) location.replace('/todos/?' + todoMatch[1]); // or # instead of ?

Then in todos/index.html you can get todoID using var todoID = location.search.substring(1) (or location.hash).

@itsezc
Copy link

itsezc commented Oct 17, 2024

I'm also blocked by this, I understand that theres a workaround but to make it easier is there a "working example" to use as a reference?

In the long term, I wish there is a way to approach this without React Router, feels like I have two routers then? (Vike Router & React Router)

@CookedApps
Copy link

@itsezc
We worked around this by using a simple hash routing approach. This doesn't require a third party router and works with Vike router as well (i.e. doesn't break it).

In our case, we just needed a way to have a user's profile under a dynamic route. Instead of having routes like /profile/username we just have /profile#username. We can read this "path" simply by using window.location.hash. We wrote some React hooks to make our lives easier.

This approach is fairly limited and will not fit every use case. Thus, I agree that dynamic routes are a necessary feature that Vike Router needs. And writing "Vike's built-in router has many features that React Router doesn't (and cannot) offer." in the docs is misleading to say the least.

@brillout
Copy link
Member Author

@itsezc AFAICT you won't need React Router after this ticket is implemented.

@CookedApps c7ce3a1 and feel free to elaborate what React Router features you're missing when using Vike's router.

@CookedApps
Copy link

@brillout

feel free to elaborate what React Router features you're missing when using Vike's router.

Client-only dynamic routes.

@itsezc
Copy link

itsezc commented Oct 20, 2024

@itsezc AFAICT you won't need React Router after this ticket is implemented.

@CookedApps c7ce3a1 and feel free to elaborate what React Router features you're missing when using Vike's router.

Good to hear @brillout, this is a huge blocker for me while using Vike for personal projects (later professionally), would it be possible to have a working example with the "workaround" which could be used as a reference (with React Router or another package you would suggest) and what sort of ETA are we looking at for this? I'm aware that its listed for v1, but just trying to "plan" ahead - won't hold you to anything.

@brillout
Copy link
Member Author

Bumping to highest-prio. I'll work on this ticket after I'm done with Vike's CLI and vike.config.js. Indeed, there isn't any ETA but I think I'll be done with it within this year.

@anuraaga
Copy link

anuraaga commented Dec 4, 2024

I ran into this as well using Firebase hosting, which allows rewrites for static generated SPAs - while I think the question about other frameworks was answered, FWIW I have had this work with other frameworks, NextJS as mentioned, and many many years ago with a webpack ssg plugin of some sort in conjunction with react router.

I found an acceptable workaround for me, don't think I see it so posting in case it helps. It just defines a fallback page that always nagivates to the URL, and rewrites to it, not index.html as is otherwise common.

https://github.com/curioswitch/aiceo/blob/main/client/src/pages/fallback/%2BPage.tsx

import { useEffect } from "react";
import { usePageContext } from "vike-react/usePageContext";
import { navigate } from "vike/client/router";

export default function Page() {
  const pageContext = usePageContext();
  useEffect(() => {
    navigate(pageContext.urlOriginal);
  }, [pageContext]);
  return undefined;
}

Firebase config, any static host more sophisticated than the most basic would have a similar setting

{
  "hosting": {
     ...
     {
        "source": "**",
        "destination": "/fallback/index.html"
      }
  }
}

I guess something built-in to Vike could behave a bit better, in my case the navbar I have in my Layout renders before the page while ideally nothing is rendered before invoking the correct route. A further workaround would be to separate the Layout for this page, but without #1692 it means having two folders, one for the app and one for just this fallback which is not worth it for my current use case since I can wait for the proper fix in this issue.

Thanks for the great framework, while this isn't the only edge I've run into, they're all minor and I'm having a good time.

@brillout
Copy link
Member Author

brillout commented Dec 4, 2024

Thank you for sharing your workaround.

this isn't the only edge

Feel free to elaborate in a new GitHub discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests