Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions integration/vite-basename-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ test.describe("Vite base + React Router basename", () => {
await workflowDev({ page, cwd, port, basename: "/mybase/app/" });
});

test("works when base and basename match the app directory name", async ({
page,
}) => {
await setup({ base: "/app/", basename: "/app/" });
await workflowDev({ page, cwd, port, base: "/app/", basename: "/app/" });
});

test("errors if basename does not start with base", async ({
page,
}) => {
Expand Down Expand Up @@ -421,6 +428,13 @@ test.describe("Vite base + React Router basename", () => {
});
});

test("works when base and basename match the app directory name", async ({
page,
}) => {
await setup({ base: "/app/", basename: "/app/" });
await workflowBuild({ page, port, base: "/app/", basename: "/app/" });
});

test("works when basename does not start with base", async ({
page,
}) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Fix `basename` conflicting with `app` directory name when Vite `base` is set

When the Vite `base` config and React Router `basename` both match the
app directory name (e.g. `base: "/app/"`, `basename: "/app/"`), Vite would
strip the base prefix from server-build virtual module import paths, causing
"Failed to load url /root.tsx" errors. The fix uses `/@fs/` absolute paths
for those imports to bypass Vite's base-stripping logic.
5 changes: 4 additions & 1 deletion packages/react-router-dev/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,9 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {

return `
import * as entryServer from ${JSON.stringify(
resolveFileUrl(ctx, ctx.entryServerFilePath),
resolveFileUrl(ctx, ctx.entryServerFilePath, {
publicPath: ctx.publicPath,
}),
)};
${Object.keys(routes)
.map((key, index) => {
Expand All @@ -834,6 +836,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
resolveFileUrl(
ctx,
resolveRelativeRouteFilePath(route, ctx.reactRouterConfig),
{ publicPath: ctx.publicPath },
),
)};`;
}
Expand Down
14 changes: 13 additions & 1 deletion packages/react-router-dev/vite/resolve-file-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getVite } from "./vite";
export const resolveFileUrl = (
{ rootDirectory }: { rootDirectory: string },
filePath: string,
{ publicPath }: { publicPath?: string } = {},
) => {
let vite = getVite();
let relativePath = path.relative(rootDirectory, filePath);
Expand All @@ -18,5 +19,16 @@ export const resolveFileUrl = (
return path.posix.join("/@fs", vite.normalizePath(filePath));
}

return "/" + vite.normalizePath(relativePath);
let url = "/" + vite.normalizePath(relativePath);

// When the Vite base config (publicPath) matches the start of the
// root-relative file URL, Vite strips the base prefix during SSR module
// loading, causing the file to not be found (e.g. basename "/app/" with
// appDirectory "app/" makes "/app/root.tsx" resolve to "/root.tsx"). Use
// the /@fs/ absolute path form to bypass Vite's base stripping.
if (publicPath && publicPath !== "/" && url.startsWith(publicPath)) {
return path.posix.join("/@fs", vite.normalizePath(filePath));
}

return url;
};
Loading