import App from "next/app";
import { AmpStateContext } from "next/dist/shared/lib/amp-context";
import { HeadManagerContext } from "next/dist/shared/lib/head-manager-context";
import Loadable from "next/dist/shared/lib/loadable";
import { LoadableContext } from "next/dist/shared/lib/loadable-context";
import { RouterContext } from "next/dist/shared/lib/router-context";
import type { NextRouter } from "next/dist/shared/lib/router/router";
import {
getDisplayName,
loadGetInitialProps,
type AppType,
type ComponentsEnhancer,
type DocumentInitialProps,
type DocumentProps,
type DocumentType,
type NextComponentType,
type RenderPage,
type RenderPageResult,
} from "next/dist/shared/lib/utils";
import { HtmlContext } from "next/dist/shared/lib/html-context";
import type { RenderOpts } from "next/dist/server/render";
import * as NextDocument from "next/document";
import * as ReactDOMServer from "react-dom/server.browser";
import * as React from "react";
import * as ReactIs from "react-is";
import packageJson from "next/package.json";
const nextVersion = packageJson.version;
function appendNextBody(documentHTML: string, pageContent: string) {
if (nextVersion.startsWith("12.0")) {
const NEXT_12_0_BODY_RENDER_TARGET = "__NEXT_BODY_RENDER_TARGET__";
const bodyRenderIdx = documentHTML.indexOf(NEXT_12_0_BODY_RENDER_TARGET);
if (!documentHTML.startsWith("")) {
documentHTML = "" + documentHTML;
}
return (
documentHTML.substring(0, bodyRenderIdx) +
pageContent +
documentHTML.substring(
bodyRenderIdx + NEXT_12_0_BODY_RENDER_TARGET.length,
)
);
} else {
var [renderTargetPrefix, renderTargetSuffix] = documentHTML.split(
"",
);
if (!renderTargetPrefix || !renderTargetSuffix) {
throw new Error(
"Can't find where your starts or where the ends. \nThis is probably a version incompatibility. Please mention this error in Bun's discord\n\n" +
documentHTML,
);
}
if (!renderTargetPrefix.startsWith("")) {
renderTargetPrefix = "" + renderTargetPrefix;
}
return (
renderTargetPrefix +
`
${pageContent || ""}
` +
renderTargetSuffix
);
}
}
const dev = process.env.NODE_ENV === "development";
type ParsedUrlQuery = Record;
const isJSFile = (file: string) =>
file.endsWith(".js") ||
file.endsWith(".jsx") ||
file.endsWith(".mjs") ||
file.endsWith(".ts") ||
file.endsWith(".tsx");
type DocumentFiles = {
sharedFiles: readonly string[];
pageFiles: readonly string[];
allFiles: readonly string[];
};
function getScripts(files: DocumentFiles) {
const { context, props } = this;
const {
assetPrefix,
buildManifest,
isDevelopment,
devOnlyCacheBusterQueryString,
} = context;
const normalScripts = files?.allFiles?.filter(isJSFile) ?? [];
const lowPriorityScripts =
buildManifest?.lowPriorityFiles?.filter(isJSFile) ?? [];
var entryPointIndex = -1;
const scripts = [...normalScripts, ...lowPriorityScripts].map(
(file, index) => {
// if (file.includes(".entry.")) {
// entryPointIndex = index;
// }
return (
);
},
);
// if (entryPointIndex > 0) {
// const entry = scripts.splice(entryPointIndex, 1);
// scripts.unshift(...entry);
// }
return scripts;
}
interface DomainLocale {
defaultLocale: string;
domain: string;
http?: true;
locales?: string[];
}
function renderDocument(
Document: DocumentType,
{
buildManifest,
docComponentsRendered,
props,
docProps,
pathname,
query,
buildId,
page,
canonicalBase,
assetPrefix,
runtimeConfig,
nextExport,
autoExport,
isFallback,
dynamicImportsIds,
dangerousAsPath,
err,
dev,
ampPath,
ampState,
inAmpMode,
hybridAmp,
dynamicImports,
headTags,
gsp,
gssp,
customServer,
gip,
appGip,
unstable_runtimeJS,
unstable_JsPreload,
devOnlyCacheBusterQueryString,
scriptLoader,
locale,
locales,
defaultLocale,
domainLocales,
isPreview,
disableOptimizedLoading,
}: RenderOpts & {
props: any;
//
page: string;
//
docComponentsRendered: DocumentProps["docComponentsRendered"];
docProps: DocumentInitialProps;
pathname: string;
query: ParsedUrlQuery;
dangerousAsPath: string;
ampState: any;
ampPath: string;
inAmpMode: boolean;
hybridAmp: boolean;
dynamicImportsIds: (string | number)[];
dynamicImports: string[];
headTags: any;
isFallback?: boolean;
gsp?: boolean;
gssp?: boolean;
customServer?: boolean;
gip?: boolean;
appGip?: boolean;
devOnlyCacheBusterQueryString: string;
scriptLoader: any;
isPreview?: boolean;
autoExport?: boolean;
},
): string {
const htmlProps = {
__NEXT_DATA__: {
props, // The result of getInitialProps
page: page, // The rendered page
query, // querystring parsed / passed by the user
buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles
assetPrefix: assetPrefix === "" ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
nextExport, // If this is a page exported by `next export`
autoExport, // If this is an auto exported page
isFallback,
dynamicIds:
dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds,
err: err || undefined, // err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
gsp, // whether the page is getStaticProps
gssp, // whether the page is getServerSideProps
customServer, // whether the user is using a custom server
gip, // whether the page has getInitialProps
appGip, // whether the _app has getInitialProps
locale,
locales,
defaultLocale,
domainLocales,
isPreview,
pages: buildManifest.pages,
},
buildManifest,
docComponentsRendered,
dangerousAsPath,
canonicalBase,
ampPath,
inAmpMode,
isDevelopment: !!dev,
hybridAmp,
dynamicImports,
assetPrefix,
headTags,
unstable_runtimeJS,
unstable_JsPreload,
devOnlyCacheBusterQueryString,
scriptLoader,
locale,
disableOptimizedLoading,
useMaybeDeferContent,
...docProps,
};
return ReactDOMServer.renderToStaticMarkup(
{/* HTMLContextProvider expects useMainContent */}
{/* @ts-expect-error */}
{/* Document doesn't expect useMaybeDeferContent */}
{/* @ts-expect-error */}
,
);
}
class ServerRouter implements NextRouter {
route: string;
pathname: string;
query: ParsedUrlQuery;
asPath: string;
basePath: string;
events: any;
isFallback: boolean;
locale?: string;
isReady: boolean;
locales?: string[];
defaultLocale?: string;
domainLocales?: DomainLocale[];
isPreview: boolean;
isLocaleDomain: boolean;
constructor(
pathname: string,
query: ParsedUrlQuery,
as: string,
{ isFallback }: { isFallback: boolean },
isReady: boolean,
basePath: string,
locale?: string,
locales?: string[],
defaultLocale?: string,
domainLocales?: DomainLocale[],
isPreview?: boolean,
isLocaleDomain?: boolean,
) {
this.route = pathname.replace(/\/$/, "") || "/";
this.pathname = new URL(
pathname || "/",
Bun.origin || "http://localhost:3000",
).href;
this.query = query;
this.asPath = new URL(
as || "/",
Bun.origin || "http://localhost:3000",
).href;
this.isFallback = isFallback;
this.basePath = basePath;
this.locale = locale;
this.locales = locales;
this.defaultLocale = defaultLocale;
this.isReady = isReady;
this.domainLocales = domainLocales;
this.isPreview = !!isPreview;
this.isLocaleDomain = !!isLocaleDomain;
}
push(): any {
noRouter();
}
replace(): any {
noRouter();
}
reload() {
noRouter();
}
back() {
noRouter();
}
prefetch(): any {
noRouter();
}
beforePopState() {
noRouter();
}
}
function noRouter() {
const message =
'No router instance found. you should only use "next/router" inside the client side of your app. https://nextjs.org/docs/messages/no-router-instance';
throw new Error(message);
}
function enhanceComponents(
options: ComponentsEnhancer,
App: AppType,
Component: NextComponentType,
): {
App: AppType;
Component: NextComponentType;
} {
// For backwards compatibility
if (typeof options === "function") {
return {
App,
Component: options(Component),
};
}
return {
App: options.enhanceApp ? options.enhanceApp(App) : App,
Component: options.enhanceComponent
? options.enhanceComponent(Component)
: Component,
};
}
const scriptsGetter = {
get() {
return getScripts;
},
};
Object.defineProperty(NextDocument.Head.prototype, "getScripts", scriptsGetter);
Object.defineProperty(
NextDocument.NextScript.prototype,
"getScripts",
scriptsGetter,
);
try {
Object.defineProperty(
NextDocument.default.prototype,
"getScripts",
scriptsGetter,
);
} catch {}
try {
Object.defineProperty(NextDocument.default, "getScripts", scriptsGetter);
} catch {}
export async function render({
route,
request,
PageNamespace,
AppNamespace,
appStylesheets = [],
pageStylesheets = [],
DocumentNamespace = null,
buildId,
routePaths = [],
routeNames = [],
}: {
buildId: number;
route: any;
PageNamespace: { default: NextComponentType };
AppNamespace: { default: NextComponentType } | null;
DocumentNamespace: Object | null;
appStylesheets: string[];
pageStylesheets: string[];
routePaths: string[];
routeNames: string[];
request: Request;
}): Promise {
const { default: Component } = PageNamespace || {};
const getStaticProps = (PageNamespace as any)?.getStaticProps || null;
const { default: AppComponent_ } = AppNamespace || {};
var query = Object.assign({}, route.query);
const origin = Bun.origin;
// These are reversed in our Router versus Next.js...mostly due to personal preference.
const pathname = route.name;
var asPath = route.pathname;
const pages = {};
for (let i = 0; i < routeNames.length; i++) {
const filePath = routePaths[i];
const name = routeNames[i];
pages[name] = [filePath];
}
if (appStylesheets.length > 0) {
if (pages["/_app"]) {
pages["/_app"].push(...appStylesheets);
} else {
pages["/_app"] = appStylesheets;
}
}
pages[pathname] = [route.scriptSrc, ...pageStylesheets];
if (!("/_app" in pages)) {
pages["/_app"] = [];
}
const AppComponent = AppComponent_ || App;
const Document = (DocumentNamespace as any)?.default || NextDocument.default;
const callMiddleware = async (method: string, args: any[], props = false) => {
let results: any = props ? {} : [];
if ((Document as any)[`${method}Middleware`]) {
let middlewareFunc = await (Document as any)[`${method}Middleware`];
middlewareFunc = middlewareFunc.default || middlewareFunc;
const curResults = await middlewareFunc(...args);
if (props) {
for (const result of curResults) {
results = {
...results,
...result,
};
}
} else {
results = curResults;
}
}
return results;
};
const headTags = (...args: any) => callMiddleware("headTags", args);
if (!ReactIs.isValidElementType(Component)) {
const exportNames = Object.keys(PageNamespace || {});
const reactComponents = exportNames.filter(ReactIs.isValidElementType);
if (reactComponents.length > 2) {
throw new Error(
`\"export default\" missing in ${
route.filePath
}.\nTry exporting one of ${reactComponents.join(", ")}\n`,
);
} else if (reactComponents.length === 2) {
throw new Error(
`\"export default\" missing in ${route.filePath}.\n\nTry exporting <${reactComponents[0]} /> or <${reactComponents[1]} />\n`,
);
} else if (reactComponents.length == 1) {
throw new Error(
`\"export default\" missing in ${route.filePath}. Try adding this to the bottom of the file:\n\n export default ${reactComponents[0]};\n`,
);
} else if (reactComponents.length == 0) {
throw new Error(
`\"export default\" missing in ${route.filePath}. Try exporting a React component.\n`,
);
}
}
const isFallback = !!query.__nextFallback;
delete query.__nextFallback;
delete query.__nextLocale;
delete query.__nextDefaultLocale;
// const isSSG = !!getStaticProps;
const defaultAppGetInitialProps =
App.getInitialProps === (App as any).origGetInitialProps;
const hasPageGetInitialProps = !!(Component as any).getInitialProps;
const pageIsDynamic = route.kind === "dynamic";
const isPreview = false;
const isAutoExport = false;
const nextExport = isAutoExport || isFallback;
if (isAutoExport || isFallback) {
// // remove query values except ones that will be set during export
// query = {
// ...(query.amp
// ? {
// amp: query.amp,
// }
// : {}),
// };
asPath = `${asPath}${
// ensure trailing slash is present for non-dynamic auto-export pages
asPath.endsWith("/") && asPath !== "/" && !pageIsDynamic ? "/" : ""
}`;
}
let head: JSX.Element[] = [
,
,
];
const reactLoadableModules: string[] = [];
var scriptLoader = {};
const AppContainer = ({ children }: any) => (
{/* */}
{
head = state;
},
updateScripts: (scripts) => {
scriptLoader = scripts;
},
scripts: {},
mountedInstances: new Set(),
}}
>
reactLoadableModules.push(moduleName)}
>
{children}
{/* */}
);
// Todo: Double check this when adding support for dynamic()
await Loadable.preloadAll(); // Make sure all dynamic imports are loaded
const router = new ServerRouter(
pathname,
query,
asPath,
{
isFallback: isFallback,
},
true,
origin,
null,
[], // renderOpts.locales,
null, //renderOpts.defaultLocale,
[], // renderOpts.domainLocales,
false,
false,
);
const ctx = {
err: null,
req: undefined,
res: undefined,
pathname,
query,
asPath,
locale: null,
locales: [],
defaultLocale: null,
AppTree: (props: any) => {
return (
);
},
defaultGetInitialProps: async (
docCtx: NextDocument.DocumentContext,
): Promise => {
const enhanceApp = (AppComp: any) => {
return (props: any) => ;
};
const { html, head } = await docCtx.renderPage({ enhanceApp });
// const styles = jsxStyleRegistry.styles();
return { html, head };
},
};
var props: any = await loadGetInitialProps(AppComponent, {
AppTree: ctx.AppTree,
Component,
router,
ctx,
});
const pageProps = Object.assign({}, props.pageProps || {});
// We don't call getServerSideProps on clients.
// @ts-expect-error
const getServerSideProps = PageNamespace.getServerSideProps;
var responseHeaders: Headers;
if (typeof getServerSideProps === "function") {
const result = await getServerSideProps({
params: route.params,
query: route.query,
req: {
destroy() {},
method: request.method,
httpVersion: "1.1",
rawHeaders: [],
rawTrailers: [],
socket: null,
statusCode: 200,
statusMessage: "OK",
trailers: {},
url: request.url,
headers: new Proxy(
{},
{
get(target, name) {
return request.headers.get(name as string);
},
has(target, name) {
return request.headers.has(name as string);
},
},
),
},
res: {
getHeaders() {
return {};
},
getHeaderNames() {
return {};
},
flushHeaders() {},
getHeader(name) {
if (!responseHeaders) return undefined;
return responseHeaders.get(name);
},
hasHeader(name) {
if (!responseHeaders) return undefined;
return responseHeaders.has(name);
},
headersSent: false,
setHeader(name, value) {
responseHeaders = responseHeaders || new Headers();
responseHeaders.set(name, String(value));
},
cork() {},
end() {},
finished: false,
},
resolvedUrl: route.pathname,
preview: false,
previewData: null,
locale: null,
locales: [],
defaultLocale: null,
});
if (result) {
if ("props" in result) {
if (typeof result.props === "object") {
Object.assign(pageProps, result.props);
}
}
}
} else if (typeof getStaticProps === "function") {
const result = await getStaticProps({
params: route.params,
query: route.query,
req: null,
res: null,
resolvedUrl: route.pathname,
preview: false,
previewData: null,
locale: null,
locales: [],
defaultLocale: null,
});
if (result) {
if ("props" in result) {
if (typeof result.props === "object") {
Object.assign(pageProps, result.props);
}
}
}
}
const renderToString = ReactDOMServer.renderToString;
const ErrorDebug = null;
props.pageProps = pageProps;
const renderPage: RenderPage = (
options: ComponentsEnhancer = {},
): RenderPageResult | Promise => {
if (ctx.err && ErrorDebug) {
const htmlOrPromise = renderToString();
return { html: htmlOrPromise, head };
}
if (dev && (props.router || props.Component)) {
throw new Error(
`'router' and 'Component' can not be returned in getInitialProps from _app.js https://nextjs.org/docs/messages/cant-override-next-props`,
);
}
const { App: EnhancedApp, Component: EnhancedComponent } =
// Argument of type 'NextComponentType | typeof App' is not assignable to parameter of type 'AppType'.
// @ts-expect-error
enhanceComponents(options, AppComponent, Component);
const htmlOrPromise = renderToString(
,
);
return { html: htmlOrPromise, head };
};
const documentCtx = { ...ctx, renderPage };
const docProps: DocumentInitialProps = await loadGetInitialProps(
Document,
documentCtx,
);
if (!docProps || typeof docProps.html !== "string") {
const message = `"${getDisplayName(
Document,
)}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`;
throw new Error(message);
}
const renderOpts = {
params: route.params,
};
const docComponentsRendered: DocumentProps["docComponentsRendered"] = {};
let html = renderDocument(Document, {
docComponentsRendered,
...renderOpts,
disableOptimizedLoading: false,
canonicalBase: origin,
buildManifest: {
devFiles: [],
allFiles: [],
polyfillFiles: [],
lowPriorityFiles: [],
// buildManifest doesn't expect pages, even though its used
// @ts-expect-error
pages,
},
// Only enabled in production as development mode has features relying on HMR (style injection for example)
// @ts-expect-error
unstable_runtimeJS: true,
// process.env.NODE_ENV === "production"
// ? pageConfig.unstable_runtimeJS
// : undefined,
// unstable_JsPreload: pageConfig.unstable_JsPreload,
// @ts-expect-error
unstable_JsPreload: true,
dangerousAsPath: router.asPath,
ampState: undefined,
props,
assetPrefix: "",
headTags: await headTags(documentCtx),
isFallback,
docProps,
page: pathname,
pathname,
ampPath: undefined,
query,
inAmpMode: false,
hybridAmp: undefined,
dynamicImportsIds: [], // Array.from(dynamicImportsIds),
dynamicImports: [], //Array.from(dynamicImports),
gsp: !!getStaticProps ? true : undefined,
gssp: !!getServerSideProps ? true : undefined,
gip: hasPageGetInitialProps ? true : undefined,
appGip: !defaultAppGetInitialProps ? true : undefined,
devOnlyCacheBusterQueryString: "",
scriptLoader,
isPreview: isPreview,
autoExport: nextExport === true ? true : undefined,
nextExport: nextExport,
useMaybeDeferContent,
});
// __NEXT_BODY_RENDER_TARGET__
html = appendNextBody(html, docProps.html);
html = html
.replaceAll('"/_next/http://', '"http://')
.replaceAll('"/_next/https://', '"https://');
if (responseHeaders) {
return new Response(html, { headers: responseHeaders });
} else {
return new Response(html);
}
}
export function useMaybeDeferContent(
_name: string,
contentFn: () => JSX.Element,
): [boolean, JSX.Element] {
return [false, contentFn()];
}