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: 8 additions & 1 deletion packages/vinext/src/server/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,14 @@ export function createSSRHandler(
return;
}
if (result && "props" in result) {
pageProps = result.props;
// Next.js explicitly supports a Promise value for `props`. Await
// it before serialising; otherwise pageProps would be a Promise
// and the rendered page would receive empty props. See
// packages/next/src/server/render.tsx (deferredContent).
pageProps =
result.props && typeof (result.props as { then?: unknown }).then === "function"
? await (result.props as Promise<Record<string, unknown>>)
: result.props;
}
if (result && "redirect" in result) {
const { redirect } = result;
Expand Down
10 changes: 9 additions & 1 deletion packages/vinext/src/server/pages-page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,15 @@ export async function resolvePagesPageData(
}

if (result?.props) {
pageProps = result.props;
// Next.js explicitly supports a Promise value for `props`. Await it
// before serialising; otherwise pageProps would be a Promise and the
// rendered page would receive empty props. See
// packages/next/src/server/render.tsx (deferredContent).
const rawProps = result.props as unknown;
pageProps =
rawProps && typeof (rawProps as { then?: unknown }).then === "function"
? await (rawProps as Promise<Record<string, unknown>>)
: (rawProps as Record<string, unknown>);
}

if (result?.redirect) {
Expand Down
22 changes: 22 additions & 0 deletions tests/fixtures/pages-basic/pages/ssr-promise-props.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
interface SSRPromisePropsPageProps {
hello: string;
count: number;
}

export default function SSRPromisePropsPage({ hello, count }: SSRPromisePropsPageProps) {
return (
<div>
<h1>SSR Promise Props</h1>
<p data-testid="hello">{hello}</p>
<p data-testid="count">count: {count}</p>
</div>
);
}

export function getServerSideProps() {
// Next.js explicitly supports a Promise value for `props`. vinext must
// await it before serialising — otherwise pageProps is empty.
return {
props: Promise.resolve({ hello: "world", count: 42 }),
};
}
32 changes: 32 additions & 0 deletions tests/pages-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,24 @@ describe("Pages Router integration", () => {
expect(html).toContain("Rendered at:");
});

// Regression test for #1459: Next.js explicitly supports a Promise value
// for `getServerSideProps` `props`. vinext must `await` the value before
// serialising — otherwise pageProps end up as a Promise and the rendered
// page shows empty values.
it("awaits Promise-shaped getServerSideProps props", async () => {
const res = await fetch(`${baseUrl}/ssr-promise-props`);
expect(res.status).toBe(200);

const html = await res.text();
expect(html).toContain("SSR Promise Props");
expect(html).toContain("world");
// React SSR inserts a `<!-- -->` comment between text and expressions.
expect(html).toMatch(/count:\s*(<!--\s*-->)?\s*42/);
// The serialized __NEXT_DATA__ payload must contain the resolved values
// (not an empty pageProps object).
expect(html).toMatch(/"pageProps":\s*\{[^}]*"hello":\s*"world"/);
});

// Regression test for #1354: when a page declares `getServerSideProps` as
// a local `const` and exports it via `export { getServerSideProps }`, the
// client-bundle transform must strip the export specifier without
Expand Down Expand Up @@ -3692,6 +3710,20 @@ describe("Production server middleware (Pages Router)", () => {
expect(await res.json()).toEqual({ ok: true, source: "gssp-res-end" });
});

// Regression test for #1459: Next.js supports a Promise value for `props`
// returned from getServerSideProps. The prod worker entry must await it
// before serialising into __NEXT_DATA__ / pageProps.
it("awaits Promise-shaped getServerSideProps props in production", async () => {
const res = await fetch(`${prodUrl}/ssr-promise-props`);
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain("SSR Promise Props");
expect(html).toContain("world");
// React SSR inserts a `<!-- -->` comment between text and expressions.
expect(html).toMatch(/count:\s*(<!--\s*-->)?\s*42/);
expect(html).toMatch(/"pageProps":\s*\{[^}]*"hello":\s*"world"/);
});

it("returns 400 for malformed percent-encoded path (not crash)", async () => {
const res = await fetch(`${prodUrl}/%E0%A4%A`);
expect(res.status).toBe(400);
Expand Down
Loading