Skip to content

Commit cd25163

Browse files
fix: address 6 bugs found in code review
1. Remove App Router pre-rendered HTML shortcut — was bypassing RSC streaming and middleware/auth pipeline 2. Move Pages Router pre-rendered check after middleware/basePath/ redirects/rewrites pipeline (step 7b instead of 1b) 3. Skip ISR pages (revalidate != false) in collectStaticRoutes() to prevent freezing dynamic content as static HTML 4. basePath handling covered by fix #2 (uses resolvedPathname) 5. Temp Vite servers now check for project vite.config and use it when present, so user plugins/aliases are available 6. vinext start guard now checks config.output directly instead of relying on out/ directory existence heuristic
1 parent 7b3686f commit cd25163

3 files changed

Lines changed: 77 additions & 45 deletions

File tree

packages/vinext/src/build/static-export.ts

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -815,16 +815,31 @@ export async function runStaticExport(
815815
}
816816

817817
// 3. Start a temporary Vite dev server
818-
const { default: vinextPlugin } = await import("../index.js");
819818
const vite = await import("vite");
820-
const server = await vite.createServer({
821-
root,
822-
configFile: false,
823-
plugins: [vinextPlugin({ appDir: root })],
824-
optimizeDeps: { holdUntilCrawlEnd: true },
825-
server: { port: 0, cors: false },
826-
logLevel: "silent",
827-
});
819+
const hasViteConfig = ["vite.config.ts", "vite.config.js", "vite.config.mjs"]
820+
.some((f) => fs.existsSync(path.join(root, f)));
821+
822+
let serverConfig: Record<string, unknown>;
823+
if (hasViteConfig) {
824+
// Use the project's vite config so user plugins/aliases/transforms are available
825+
serverConfig = {
826+
root,
827+
optimizeDeps: { holdUntilCrawlEnd: true },
828+
server: { port: 0, cors: false },
829+
logLevel: "silent",
830+
};
831+
} else {
832+
const { default: vinextPlugin } = await import("../index.js");
833+
serverConfig = {
834+
root,
835+
configFile: false,
836+
plugins: [vinextPlugin({ appDir: root })],
837+
optimizeDeps: { holdUntilCrawlEnd: true },
838+
server: { port: 0, cors: false },
839+
logLevel: "silent",
840+
};
841+
}
842+
const server = await vite.createServer(serverConfig);
828843
await server.listen();
829844

830845
try {
@@ -1000,16 +1015,30 @@ async function collectStaticRoutes(root: string, isAppRouter: boolean): Promise<
10001015
if (!isAppRouter && !pagesDir) return [];
10011016

10021017
// Start a temporary Vite dev server for module inspection
1003-
const { default: vinextPlugin } = await import("../index.js");
10041018
const vite = await import("vite");
1005-
const server = await vite.createServer({
1006-
root,
1007-
configFile: false,
1008-
plugins: [vinextPlugin({ appDir: root })],
1009-
optimizeDeps: { holdUntilCrawlEnd: true },
1010-
server: { port: 0, cors: false },
1011-
logLevel: "silent",
1012-
});
1019+
const hasViteConfig = ["vite.config.ts", "vite.config.js", "vite.config.mjs"]
1020+
.some((f) => fs.existsSync(path.join(root, f)));
1021+
1022+
let serverConfig: Record<string, unknown>;
1023+
if (hasViteConfig) {
1024+
serverConfig = {
1025+
root,
1026+
optimizeDeps: { holdUntilCrawlEnd: true },
1027+
server: { port: 0, cors: false },
1028+
logLevel: "silent",
1029+
};
1030+
} else {
1031+
const { default: vinextPlugin } = await import("../index.js");
1032+
serverConfig = {
1033+
root,
1034+
configFile: false,
1035+
plugins: [vinextPlugin({ appDir: root })],
1036+
optimizeDeps: { holdUntilCrawlEnd: true },
1037+
server: { port: 0, cors: false },
1038+
logLevel: "silent",
1039+
};
1040+
}
1041+
const server = await vite.createServer(serverConfig);
10131042
await server.listen();
10141043

10151044
try {
@@ -1025,8 +1054,9 @@ async function collectStaticRoutes(root: string, isAppRouter: boolean): Promise<
10251054
try {
10261055
const pageModule = await server.ssrLoadModule(route.pagePath);
10271056

1028-
// Skip force-dynamic pages
1057+
// Skip dynamic/request-dependent pages
10291058
if (pageModule.dynamic === "force-dynamic") continue;
1059+
if (pageModule.revalidate !== undefined && pageModule.revalidate !== false) continue;
10301060

10311061
if (route.isDynamic) {
10321062
// Need generateStaticParams to expand
@@ -1074,6 +1104,16 @@ async function collectStaticRoutes(root: string, isAppRouter: boolean): Promise<
10741104
// Skip pages with getServerSideProps
10751105
if (typeof pageModule.getServerSideProps === "function") continue;
10761106

1107+
// Skip ISR pages (getStaticProps with revalidate)
1108+
if (typeof pageModule.getStaticProps === "function") {
1109+
try {
1110+
const propsResult = await pageModule.getStaticProps({});
1111+
if (propsResult?.revalidate) continue;
1112+
} catch {
1113+
continue; // Skip if getStaticProps fails
1114+
}
1115+
}
1116+
10771117
if (route.isDynamic) {
10781118
// Need getStaticPaths with fallback: false
10791119
if (typeof pageModule.getStaticPaths !== "function") continue;

packages/vinext/src/cli.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,12 @@ async function start() {
337337
});
338338

339339
// Reject static export builds — they don't need a production server
340-
const outExportDir = path.resolve(process.cwd(), "out");
341-
const distServerDir = path.resolve(process.cwd(), "dist", "server");
342-
if (
343-
fs.existsSync(path.join(outExportDir, "index.html")) &&
344-
!fs.existsSync(distServerDir)
345-
) {
340+
const { loadNextConfig, resolveNextConfig } = await import(
341+
/* @vite-ignore */ "./config/next-config.js"
342+
);
343+
const startRawConfig = await loadNextConfig(process.cwd());
344+
const startResolvedConfig = await resolveNextConfig(startRawConfig);
345+
if (startResolvedConfig.output === "export") {
346346
console.error(
347347
'\n "vinext start" does not work with "output: export" configuration.',
348348
);

packages/vinext/src/server/prod-server.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -576,15 +576,6 @@ async function startAppRouterServer(options: AppRouterServerOptions) {
576576
return;
577577
}
578578

579-
// Pre-rendered HTML — serve build-time rendered pages before hitting RSC/SSR
580-
const appPrerenderedDir = path.join(path.dirname(rscEntryPath), "pages");
581-
const appPrerenderedFile = resolvePrerenderedHtml(appPrerenderedDir, pathname);
582-
if (appPrerenderedFile) {
583-
const html = fs.readFileSync(appPrerenderedFile, "utf-8");
584-
sendCompressed(req, res, html, "text/html; charset=utf-8", 200, {}, compress);
585-
return;
586-
}
587-
588579
try {
589580
// Convert Node.js request to Web Request and call the RSC handler
590581
const request = nodeToWebRequest(req);
@@ -751,17 +742,6 @@ async function startPagesRouterServer(options: PagesRouterServerOptions) {
751742
return;
752743
}
753744

754-
// ── 1b. Pre-rendered HTML ──────────────────────────────────
755-
// Check if a pre-rendered HTML file exists for this pathname.
756-
// These are generated during `vinext build` for static pages.
757-
const pagesPrerenderedDir = path.join(path.dirname(serverEntryPath), "pages");
758-
const pagesPrerenderedFile = resolvePrerenderedHtml(pagesPrerenderedDir, pathname);
759-
if (pagesPrerenderedFile) {
760-
const html = fs.readFileSync(pagesPrerenderedFile, "utf-8");
761-
sendCompressed(req, res, html, "text/html; charset=utf-8", 200, {}, compress);
762-
return;
763-
}
764-
765745
try {
766746
// ── 2. Strip basePath ─────────────────────────────────────────
767747
if (basePath && pathname.startsWith(basePath)) {
@@ -930,6 +910,18 @@ async function startPagesRouterServer(options: PagesRouterServerOptions) {
930910
}
931911
}
932912

913+
// ── 7b. Pre-rendered HTML ─────────────────────────────────────
914+
// Serve build-time rendered static pages. Placed after middleware,
915+
// basePath stripping, redirects, and rewrites so those all run first.
916+
const pagesPrerenderedDir = path.join(path.dirname(serverEntryPath), "pages");
917+
const pagesPrerenderedFile = resolvePrerenderedHtml(pagesPrerenderedDir, resolvedPathname);
918+
if (pagesPrerenderedFile) {
919+
const html = fs.readFileSync(pagesPrerenderedFile, "utf-8");
920+
const prerenderedHeaders: Record<string, string> = { ...middlewareHeaders };
921+
sendCompressed(req, res, html, "text/html; charset=utf-8", 200, prerenderedHeaders, compress);
922+
return;
923+
}
924+
933925
// ── 8. API routes ─────────────────────────────────────────────
934926
if (resolvedPathname.startsWith("/api/") || resolvedPathname === "/api") {
935927
let response: Response;

0 commit comments

Comments
 (0)