Skip to content
Open
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
9 changes: 9 additions & 0 deletions packages/vinext/src/build/prerender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -897,12 +897,21 @@ export async function prerenderApp({

rscHandler = (req: Request) => {
// Forward the request to the local prod server.
// `redirect: "manual"` ensures pages that call `redirect()` surface as
// their original 3xx response — otherwise fetch follows the Location
// header server-side, the prerender harness sees a 200 for the
// destination page, and that destination HTML gets written under the
// redirecting route's filename. At runtime the prod server then serves
// the cached HTML with status 200 instead of emitting a 307 for the
// document load. Mirrors the pages-prerender `renderPage` helper above.
// See: https://github.com/cloudflare/vinext/issues/1530
const parsed = new URL(req.url);
const url = `${baseUrl}${parsed.pathname}${parsed.search}`;
return fetch(url, {
method: req.method,
headers: { ...secretHeaders, ...Object.fromEntries(req.headers.entries()) },
body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined,
redirect: "manual",
});
};

Expand Down
17 changes: 17 additions & 0 deletions tests/app-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2440,6 +2440,23 @@ describe("App Router Production server (startProdServer)", () => {
expect(res.status).toBe(404);
});

// Ported from Next.js: test/e2e/app-dir/rsc-redirect/rsc-redirect.test.ts
// https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/rsc-redirect/rsc-redirect.test.ts
//
// Document request (no `Rsc` header) to a page that calls `redirect()` must
// respond with HTTP 307 + Location. The RSC variant (`.rsc` URL or Rsc:1
// header) returns 200 with a flight payload — that path is covered by the
// sibling `.rsc` redirect tests above and by issue #1347.
//
// See: https://github.com/cloudflare/vinext/issues/1530
it("redirect() from Server Component returns 307 on document load (production)", async () => {
const res = await fetch(`${baseUrl}/redirect-test`, { redirect: "manual" });
expect(res.status).toBe(307);
const location = res.headers.get("location");
expect(location).toBeTruthy();
expect(location).toContain("/about");
});

it("serves static assets with cache headers", async () => {
// Find an actual hashed asset from the build (on disk under
// `_next/static/`, matching `resolveAssetsDir("")`).
Expand Down
21 changes: 21 additions & 0 deletions tests/prerender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,27 @@ describe("prerenderApp — default mode (app-basic)", () => {
expect(r?.status).toBe("skipped");
});

// Ported from Next.js: test/e2e/app-dir/rsc-redirect/rsc-redirect.test.ts
// ('should get 307 status code for document request')
//
// A speculative prerender of a route that calls `redirect()` must not
// follow the redirect server-side and cache the destination's HTML under
// the redirecting URL. Doing so makes the prod server reply with 200 and
// the destination's body on every document request to the redirecting
// route, instead of emitting an HTTP 307 with a Location header.
//
// See: https://github.com/cloudflare/vinext/issues/1530
it("skips /redirect-test instead of capturing the destination HTML", () => {
const r = findRoute(results, "/redirect-test");
expect(r).toBeDefined();
expect(r?.status).toBe("skipped");
// No HTML/RSC must be written for the redirecting route — otherwise the
// prod server serves the cached destination body with status 200 for
// every document request to /redirect-test.
expect(fs.existsSync(path.join(outDir, "redirect-test.html"))).toBe(false);
expect(fs.existsSync(path.join(outDir, "redirect-test.rsc"))).toBe(false);
});

// ── API routes — always skipped ────────────────────────────────────────────

it("skips all API route handlers", () => {
Expand Down
Loading