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
1 change: 1 addition & 0 deletions packages/vinext/src/entries/pages-client-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ async function hydrate() {
console.error("[vinext] Page module has no default export");
return;
}
window.__VINEXT_PAGE_COMPONENT__ = PageComponent;

let element;
${
Expand Down
3 changes: 3 additions & 0 deletions packages/vinext/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ declare global {
}>
| undefined;

/** The currently committed Pages Router page component. */
__VINEXT_PAGE_COMPONENT__: React.ComponentType<Record<string, unknown>> | undefined;

/**
* Pages Router code-split loader map. Keys are route patterns in Next.js
* bracket format (e.g. `/blog/[slug]`), values are dynamic `import()`
Expand Down
150 changes: 97 additions & 53 deletions packages/vinext/src/server/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,22 @@ export function createSSRHandler(
const gsspExtraHeaders: Record<string, string | string[]> = {};

const hasAppGetInitialProps = hasPagesGetInitialProps(AppComponent);
const createAppTree = (appTreeProps: Record<string, unknown>) => {
const appTree = AppComponent
? React.createElement(AppComponent, {
...appTreeProps,
Component: PageComponent,
pageProps: appTreeProps.pageProps,
router: routerShim.default,
})
: React.createElement(
PageComponent,
isUnknownRecord(appTreeProps.pageProps) ? appTreeProps.pageProps : {},
);
return typeof routerShim.wrapWithRouterContext === "function"
? routerShim.wrapWithRouterContext(appTree)
: appTree;
};

// Thin glue over loadDevAppInitialProps: build the React AppTree closure,
// delegate the decision to the tested helper, and apply the result.
Expand All @@ -966,17 +982,7 @@ export function createSSRHandler(
}
const appResult = await loadDevAppInitialProps({
appComponent: AppComponent,
appTree: (appTreeProps: Record<string, unknown>) => {
const appTree = React.createElement(AppComponent, {
...appTreeProps,
Component: PageComponent,
pageProps: appTreeProps.pageProps,
router: routerShim.default,
});
return typeof routerShim.wrapWithRouterContext === "function"
? routerShim.wrapWithRouterContext(appTree)
: appTree;
},
appTree: createAppTree,
component: PageComponent,
req,
res,
Expand Down Expand Up @@ -1281,8 +1287,9 @@ export function createSSRHandler(
return {};
},
};
const initialProps = await loadPagesGetInitialProps(RegenApp, {
AppTree: (appTreeProps: Record<string, unknown>) => {
const appResult = await loadDevAppInitialProps({
appComponent: RegenApp,
appTree: (appTreeProps: Record<string, unknown>) => {
const appTree = React.createElement(RegenApp, {
...appTreeProps,
Component: pageModule.default,
Expand All @@ -1293,29 +1300,20 @@ export function createSSRHandler(
? routerShim.wrapWithRouterContext(appTree)
: appTree;
},
Component: pageModule.default,
router: {
pathname: patternToNextFormat(route.pattern),
query,
asPath: requestAsPath,
},
ctx: {
req: regenReq,
res: regenRes,
pathname: patternToNextFormat(route.pattern),
query,
asPath: requestAsPath,
locale: locale ?? currentDefaultLocale,
locales: i18nConfig?.locales,
defaultLocale: currentDefaultLocale,
},
component: pageModule.default,
req: regenReq,
res: regenRes,
pathname: patternToNextFormat(route.pattern),
query,
asPath: requestAsPath,
locale: locale ?? currentDefaultLocale,
locales: i18nConfig?.locales,
defaultLocale: currentDefaultLocale,
});
if (regenRes.headersSent || regenRes.writableEnded) return;
if (initialProps) {
freshRenderProps = initialProps;
freshPageProps = isUnknownRecord(initialProps.pageProps)
? initialProps.pageProps
: {};
if (appResult.kind === "response-sent") return;
if (appResult.kind === "render") {
freshRenderProps = appResult.renderProps;
freshPageProps = appResult.pageProps;
}
}

Expand Down Expand Up @@ -1573,6 +1571,7 @@ export function createSSRHandler(
!hasAppGetInitialProps
) {
const initialProps = await loadPagesGetInitialProps(PageComponent, {
AppTree: createAppTree,
req,
res,
pathname: patternToNextFormat(route.pattern),
Expand Down Expand Up @@ -1809,6 +1808,7 @@ async function hydrate() {

const pageModule = await import("${pageModuleSource}");
const PageComponent = pageModule.default;
window.__VINEXT_PAGE_COMPONENT__ = PageComponent;
let element;
${
appModuleSource
Expand Down Expand Up @@ -2115,28 +2115,71 @@ async function renderErrorPage(
}

const createElement = React.createElement;
const initialErrorProps = await loadPagesGetInitialProps(ErrorComponent, {
req,
res,
err,
pathname: candidate === "_error" ? "/_error" : `/${candidate}`,
query: parseQuery(url),
asPath: url,
});
if (res.headersSent || res.writableEnded) return;
const errorProps = { ...initialErrorProps, statusCode };

// If the caller didn't supply wrapWithRouterContext, load it now.
// runner.import() caches internally so the cost is negligible.
let wrapFn = wrapWithRouterContext;
if (!wrapFn) {
try {
const errRouterShim = await importModule(runner, "next/router");
wrapFn = errRouterShim.wrapWithRouterContext;
} catch {
// router shim not available — continue without it
let errorRouter: Record<string, unknown> = {};
try {
const errRouterShim = await importModule(runner, "next/router");
if (!wrapFn) wrapFn = errRouterShim.wrapWithRouterContext;
errorRouter = (errRouterShim.default as Record<string, unknown> | undefined) ?? {};
} catch {
// router shim not available — continue without it
}

const errorPathname = candidate === "_error" ? "/_error" : `/${candidate}`;
const errorQuery = parseQuery(url);
const createErrorAppTree = (appTreeProps: Record<string, unknown>) => {
let appTree: React.ReactElement = AppComponent
? createElement(AppComponent, {
...appTreeProps,
Component: ErrorComponent,
pageProps: appTreeProps.pageProps,
router: errorRouter,
})
: createElement(
ErrorComponent,
isUnknownRecord(appTreeProps.pageProps) ? appTreeProps.pageProps : {},
);
if (wrapFn) appTree = wrapFn(appTree);
return appTree;
};

let renderProps: Record<string, unknown> = { pageProps: {} };
let errorProps: Record<string, unknown> = {};
if (AppComponent && hasPagesGetInitialProps(AppComponent)) {
const appResult = await loadDevAppInitialProps({
appComponent: AppComponent,
appTree: createErrorAppTree,
component: ErrorComponent,
req,
res,
err,
pathname: errorPathname,
query: errorQuery,
asPath: url,
});
if (appResult.kind === "response-sent") return;
if (appResult.kind === "render") {
errorProps = appResult.pageProps;
renderProps = appResult.renderProps;
}
} else {
const initialErrorProps = await loadPagesGetInitialProps(ErrorComponent, {
AppTree: createErrorAppTree,
req,
res,
err,
pathname: errorPathname,
query: errorQuery,
asPath: url,
});
if (res.headersSent || res.writableEnded) return;
errorProps = initialErrorProps ?? {};
renderProps = { pageProps: errorProps };
}
errorProps = { ...errorProps, statusCode };
renderProps = { ...renderProps, pageProps: errorProps };

// Try custom _document
// oxlint-disable-next-line typescript/no-explicit-any
Expand All @@ -2159,8 +2202,10 @@ async function renderErrorPage(
): React.ReactElement => {
let errorElement: React.ReactElement = FinalApp
? createElement(FinalApp, {
...renderProps,
Component: FinalComponent,
pageProps: errorProps,
router: errorRouter,
})
: createElement(FinalComponent, errorProps);
if (wrapFn) errorElement = wrapFn(errorElement);
Expand All @@ -2181,7 +2226,6 @@ async function renderErrorPage(
);

if (DocumentComponent) {
const errorPathname = candidate === "_error" ? "/_error" : `/${candidate}`;
await streamPageToResponse(res, element, {
url,
server,
Expand Down
3 changes: 3 additions & 0 deletions packages/vinext/src/server/pages-get-initial-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export type DevAppInitialPropsContext = {
component: unknown;
req: unknown;
res: unknown;
err?: unknown;
pathname: string;
query: Record<string, unknown>;
asPath: string;
Expand Down Expand Up @@ -153,8 +154,10 @@ export async function loadDevAppInitialProps(
Component: ctx.component,
router: { pathname: ctx.pathname, query: ctx.query, asPath: ctx.asPath },
ctx: {
AppTree: ctx.appTree,
req: ctx.req,
res: ctx.res,
err: ctx.err,
pathname: ctx.pathname,
query: ctx.query,
asPath: ctx.asPath,
Expand Down
3 changes: 3 additions & 0 deletions packages/vinext/src/server/pages-page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ async function loadPagesAppInitialRenderProps(
asPath: options.asPath ?? options.routeUrl,
},
ctx: {
AppTree: options.createAppTree ?? options.createPageElement,
req,
res,
err: options.err,
Expand Down Expand Up @@ -1091,7 +1092,9 @@ export async function resolvePagesPageData(
hasPagesGetInitialProps(options.pageModule.default)
) {
const { req, res, responsePromise } = getSharedReqRes();
const AppTree = options.createAppTree ?? options.createPageElement;
const initialProps = await loadPagesGetInitialProps(options.pageModule.default, {
AppTree,
req,
res,
err: options.err,
Expand Down
Loading
Loading