react-router
react-router copied to clipboard
[Bug]: v7pre - Can't use vite preview with SPA + basename + prerender
What version of React Router are you using?
7.0.0-pre.0
Steps to Reproduce
- Enable SPA mode (
ssr: false) - Set the basename to /chat/
- Enable prerender.
- Build the app with
npm run build - Run
vite preview.
Minimal reproduction (run npm run build + npm run preview on it):
https://stackblitz.com/edit/react-router-v7-basename-prerender-preview?file=vite.config.ts
Expected Behavior
When opening http://localhost:4173/chat/, the preview server responds with /build/client/chat/index.html and the root route is rendered.
Actual Behavior
There is a 404 error response. When opening http://localhost:4173/chat/, vite tries to respond with the file /build/client/index.html which doesn't exist (the prerendered route is in /build/client/chat/index.html), so there is a 404 response.
For now I've solved it adding this plugin to vite config, but it doesn't seem very clean
{
name: "configure-preview-server",
configurePreviewServer(server) {
return () => {
server.middlewares.use((req, res, next) => {
if (req.url === "/index.html") {
req.url = `${req.originalUrl?.slice(0, -1)}${req.url}`;
}
next();
});
};
}
}
I'm not quite sure what to do here - with basename: '/chat/', React Router has to generate the file inside of a chat/ directory so it can be deployed to a static file CDN and served at chat/.
But then when Vite's base is also set, Vite strips that from any incoming request during vite preview it seems and hence the 404. You need to go to /chat/chat to get Vite to serve the file, and then that fails to hydrate because the basename doesn't match.
I think vite preview works as expected if you just use basename and skip base - is that an option is your setup?
It does work but scripts will have href="/assets/...".
I need the href for scripts to begin with /chat (such as href="/chat/assets/entry.client-BmgBuKBB.js"), not only the routes.
AFAIK the only way to do that is setting base: '/chat/'.
I think we have a check somewhere that basename has to start with base so in theory we could strip any base from basename when we generate the files, but that would mean you can no longer run your app via a simple HTTP server like npx http-server build/client because they wouldn't live in /chat anymore and http-server doesn't know to add /chat since that's a vite preview only thing...
I'll see if I can find some time to play around with these combinations and see if there's a good solution that works for all cases.
It's worth mentioning that Vite allows using base: "./" which I've found works quite well for static/prerendered sites. All assets are looked up relatively, avoiding any issues with deeply-nested absolute paths.
I'm not quite sure what to do here - with
basename: '/chat/', React Router has to generate the file inside of achat/directory so it can be deployed to a static file CDN and served atchat/.
@brophdawg11 I think this assumption might be incorrect? The base name is often used for deploying to a sub-directory that already exists. This is the case with GitHub Pages, where you are basically forced to use a sub-path, even if you deploy to the root.
Currently, using reactRouter({ basename: "/chat/", prerender: true, ssr: false }) will generate two directories: build/client/chat/ and build/client/assets/. This makes it very difficult to deploy. It would be easier if the assets/ directory was inside chat/.
~~Maybe unrelated: I also noticed a strange issue after deploying the build/client/ directory as-is. Prerendered pages work when visiting the URLs directly, but don't work at all navigating on the client using <Link>.~~ Edit: This is indeed unrelated; gh-pages was ignoring the Remix __manifest file by default (see more).
Don't know if this is the right discussion, however with the following config
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
// Disable SSR since we want static pre-rendering
ssr: false,
basename: "/syb-interview-ai/",
// Pre-render all routes at build time
async prerender() {
return ["/", "/user"];
},
} satisfies Config;
// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [reactRouter(), tsconfigPaths()],
base: "/syb-interview-ai/",
});
I get this when running build
There's no way this will work, and i have to move files manually to get to the proper paths
To make it work i have to move the favicon and the assets manually in the syb-interview-ai folder. You also have to be sure that the base in vite ends with a slash otheriwise the build produces paths like syb-interview-aiassets. This slash thing is also a problem because it means that you can't make the website work without a trailing / in the url because react router won't render anything.
As a side note i don't even understand what that __spa-fallback.html should be needed for.
I'm not quite sure what to do here - with
basename: '/chat/', React Router has to generate the file inside of achat/directory so it can be deployed to a static file CDN and served atchat/.@brophdawg11 I think this assumption might be incorrect? The base name is often used for deploying to a sub-directory that already exists. This is the case with GitHub Pages, where you are basically forced to use a sub-path, even if you deploy to the root.
Currently, using
reactRouter({ basename: "/chat/", prerender: true, ssr: false })will generate two directories:build/client/chat/andbuild/client/assets/. This makes it very difficult to deploy. It would be easier if theassets/directory was insidechat/.~Maybe unrelated: I also noticed a strange issue after deploying the
build/client/directory as-is. Prerendered pages work when visiting the URLs directly, but don't work at all navigating on the client using<Link>.~ Edit: This is indeed unrelated; gh-pages was ignoring the Remix__manifestfile by default (see more).
this is actually achievable, what you need to do is set build.assetsDir in vite.config.ts to your basename (but without the slashes) while keeping the base unset.
this is actually achievable, what you need to do is set
build.assetsDirinvite.config.tsto your basename (but without the slashes) while keeping thebaseunset.
I was already setting build.assetsDir to a subdirectory under basename. It fixes the asset paths, but I ran into some really strange issues (e.g. stuff missing in .html) which I've honestly struggled to understand and gave up after a while.
This conversation might be better suited for #13615.
🤖 Hello there,
We just published version 7.8.0-pre.3 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!
Thanks!
🤖 bad bot - this is not in 7.8.0 and will be in the next release
🤖 Hello there,
We just published version 7.8.1-pre.0 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!
Thanks!
🤖 Hello there,
We just published version 7.8.1 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!
Thanks!
Hello there, I am running into this same issue, not sure if someone has any workaround? Using v7.8.1
Hello there, I am running into this same issue, not sure if someone has any workaround? Using v7.8.1
Hello, hmm. My issues were resolved after the fix in 7.8.1, make sure your base in vite.config.ts is unset and set the build.assetsDir to your react-router.cofig.ts basename (without the first slash). And that all your pages are in the prerender config.