including-modules
This commit is contained in:
+342
@@ -0,0 +1,342 @@
|
||||
import { AppRouteRouteModule } from '../../server/route-modules/app-route/module.compiled';
|
||||
import { RouteKind } from '../../server/route-kind';
|
||||
import { patchFetch as _patchFetch } from '../../server/lib/patch-fetch';
|
||||
import { addRequestMeta, getRequestMeta } from '../../server/request-meta';
|
||||
import { getTracer, SpanKind } from '../../server/lib/trace/tracer';
|
||||
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton';
|
||||
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths';
|
||||
import { NodeNextRequest, NodeNextResponse } from '../../server/base-http/node';
|
||||
import { NextRequestAdapter, signalFromNodeResponse } from '../../server/web/spec-extension/adapters/next-request';
|
||||
import { BaseServerSpan } from '../../server/lib/trace/constants';
|
||||
import { getRevalidateReason } from '../../server/instrumentation/utils';
|
||||
import { sendResponse } from '../../server/send-response';
|
||||
import { fromNodeOutgoingHttpHeaders, toNodeOutgoingHttpHeaders } from '../../server/web/utils';
|
||||
import { getCacheControlHeader } from '../../server/lib/cache-control';
|
||||
import { INFINITE_CACHE, NEXT_CACHE_TAGS_HEADER } from '../../lib/constants';
|
||||
import { NoFallbackError } from '../../shared/lib/no-fallback-error.external';
|
||||
import { CachedRouteKind } from '../../server/response-cache';
|
||||
import * as userland from 'VAR_USERLAND';
|
||||
// We inject the nextConfigOutput here so that we can use them in the route
|
||||
// module.
|
||||
// INJECT:nextConfigOutput
|
||||
const routeModule = new AppRouteRouteModule({
|
||||
definition: {
|
||||
kind: RouteKind.APP_ROUTE,
|
||||
page: 'VAR_DEFINITION_PAGE',
|
||||
pathname: 'VAR_DEFINITION_PATHNAME',
|
||||
filename: 'VAR_DEFINITION_FILENAME',
|
||||
bundlePath: 'VAR_DEFINITION_BUNDLE_PATH'
|
||||
},
|
||||
distDir: process.env.__NEXT_RELATIVE_DIST_DIR || '',
|
||||
relativeProjectDir: process.env.__NEXT_RELATIVE_PROJECT_DIR || '',
|
||||
resolvedPagePath: 'VAR_RESOLVED_PAGE_PATH',
|
||||
nextConfigOutput,
|
||||
userland
|
||||
});
|
||||
// Pull out the exports that we need to expose from the module. This should
|
||||
// be eliminated when we've moved the other routes to the new format. These
|
||||
// are used to hook into the route.
|
||||
const { workAsyncStorage, workUnitAsyncStorage, serverHooks } = routeModule;
|
||||
function patchFetch() {
|
||||
return _patchFetch({
|
||||
workAsyncStorage,
|
||||
workUnitAsyncStorage
|
||||
});
|
||||
}
|
||||
export { routeModule, workAsyncStorage, workUnitAsyncStorage, serverHooks, patchFetch, };
|
||||
export async function handler(req, res, ctx) {
|
||||
if (routeModule.isDev) {
|
||||
addRequestMeta(req, 'devRequestTimingInternalsEnd', process.hrtime.bigint());
|
||||
}
|
||||
let srcPage = 'VAR_DEFINITION_PAGE';
|
||||
// turbopack doesn't normalize `/index` in the page name
|
||||
// so we need to to process dynamic routes properly
|
||||
// TODO: fix turbopack providing differing value from webpack
|
||||
if (process.env.TURBOPACK) {
|
||||
srcPage = srcPage.replace(/\/index$/, '') || '/';
|
||||
} else if (srcPage === '/index') {
|
||||
// we always normalize /index specifically
|
||||
srcPage = '/';
|
||||
}
|
||||
const multiZoneDraftMode = process.env.__NEXT_MULTI_ZONE_DRAFT_MODE;
|
||||
const prepareResult = await routeModule.prepare(req, res, {
|
||||
srcPage,
|
||||
multiZoneDraftMode
|
||||
});
|
||||
if (!prepareResult) {
|
||||
res.statusCode = 400;
|
||||
res.end('Bad Request');
|
||||
ctx.waitUntil == null ? void 0 : ctx.waitUntil.call(ctx, Promise.resolve());
|
||||
return null;
|
||||
}
|
||||
const { buildId, params, nextConfig, parsedUrl, isDraftMode, prerenderManifest, routerServerContext, isOnDemandRevalidate, revalidateOnlyGenerated, resolvedPathname, clientReferenceManifest, serverActionsManifest } = prepareResult;
|
||||
const normalizedSrcPage = normalizeAppPath(srcPage);
|
||||
let isIsr = Boolean(prerenderManifest.dynamicRoutes[normalizedSrcPage] || prerenderManifest.routes[resolvedPathname]);
|
||||
const render404 = async ()=>{
|
||||
// TODO: should route-module itself handle rendering the 404
|
||||
if (routerServerContext == null ? void 0 : routerServerContext.render404) {
|
||||
await routerServerContext.render404(req, res, parsedUrl, false);
|
||||
} else {
|
||||
res.end('This page could not be found');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
if (isIsr && !isDraftMode) {
|
||||
const isPrerendered = Boolean(prerenderManifest.routes[resolvedPathname]);
|
||||
const prerenderInfo = prerenderManifest.dynamicRoutes[normalizedSrcPage];
|
||||
if (prerenderInfo) {
|
||||
if (prerenderInfo.fallback === false && !isPrerendered) {
|
||||
if (nextConfig.experimental.adapterPath) {
|
||||
return await render404();
|
||||
}
|
||||
throw new NoFallbackError();
|
||||
}
|
||||
}
|
||||
}
|
||||
let cacheKey = null;
|
||||
if (isIsr && !routeModule.isDev && !isDraftMode) {
|
||||
cacheKey = resolvedPathname;
|
||||
// ensure /index and / is normalized to one key
|
||||
cacheKey = cacheKey === '/index' ? '/' : cacheKey;
|
||||
}
|
||||
const supportsDynamicResponse = // If we're in development, we always support dynamic HTML
|
||||
routeModule.isDev === true || // If this is not SSG or does not have static paths, then it supports
|
||||
// dynamic HTML.
|
||||
!isIsr;
|
||||
// This is a revalidation request if the request is for a static
|
||||
// page and it is not being resumed from a postponed render and
|
||||
// it is not a dynamic RSC request then it is a revalidation
|
||||
// request.
|
||||
const isStaticGeneration = isIsr && !supportsDynamicResponse;
|
||||
// Before rendering (which initializes component tree modules), we have to
|
||||
// set the reference manifests to our global store so Server Action's
|
||||
// encryption util can access to them at the top level of the page module.
|
||||
if (serverActionsManifest && clientReferenceManifest) {
|
||||
setManifestsSingleton({
|
||||
page: srcPage,
|
||||
clientReferenceManifest,
|
||||
serverActionsManifest
|
||||
});
|
||||
}
|
||||
const method = req.method || 'GET';
|
||||
const tracer = getTracer();
|
||||
const activeSpan = tracer.getActiveScopeSpan();
|
||||
const context = {
|
||||
params,
|
||||
prerenderManifest,
|
||||
renderOpts: {
|
||||
experimental: {
|
||||
authInterrupts: Boolean(nextConfig.experimental.authInterrupts)
|
||||
},
|
||||
cacheComponents: Boolean(nextConfig.cacheComponents),
|
||||
supportsDynamicResponse,
|
||||
incrementalCache: getRequestMeta(req, 'incrementalCache'),
|
||||
cacheLifeProfiles: nextConfig.cacheLife,
|
||||
waitUntil: ctx.waitUntil,
|
||||
onClose: (cb)=>{
|
||||
res.on('close', cb);
|
||||
},
|
||||
onAfterTaskError: undefined,
|
||||
onInstrumentationRequestError: (error, _request, errorContext, silenceLog)=>routeModule.onRequestError(req, error, errorContext, silenceLog, routerServerContext)
|
||||
},
|
||||
sharedContext: {
|
||||
buildId
|
||||
}
|
||||
};
|
||||
const nodeNextReq = new NodeNextRequest(req);
|
||||
const nodeNextRes = new NodeNextResponse(res);
|
||||
const nextReq = NextRequestAdapter.fromNodeNextRequest(nodeNextReq, signalFromNodeResponse(res));
|
||||
try {
|
||||
const invokeRouteModule = async (span)=>{
|
||||
return routeModule.handle(nextReq, context).finally(()=>{
|
||||
if (!span) return;
|
||||
span.setAttributes({
|
||||
'http.status_code': res.statusCode,
|
||||
'next.rsc': false
|
||||
});
|
||||
const rootSpanAttributes = tracer.getRootSpanAttributes();
|
||||
// We were unable to get attributes, probably OTEL is not enabled
|
||||
if (!rootSpanAttributes) {
|
||||
return;
|
||||
}
|
||||
if (rootSpanAttributes.get('next.span_type') !== BaseServerSpan.handleRequest) {
|
||||
console.warn(`Unexpected root span type '${rootSpanAttributes.get('next.span_type')}'. Please report this Next.js issue https://github.com/vercel/next.js`);
|
||||
return;
|
||||
}
|
||||
const route = rootSpanAttributes.get('next.route');
|
||||
if (route) {
|
||||
const name = `${method} ${route}`;
|
||||
span.setAttributes({
|
||||
'next.route': route,
|
||||
'http.route': route,
|
||||
'next.span_name': name
|
||||
});
|
||||
span.updateName(name);
|
||||
} else {
|
||||
span.updateName(`${method} ${srcPage}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
const isMinimalMode = Boolean(process.env.MINIMAL_MODE || getRequestMeta(req, 'minimalMode'));
|
||||
const handleResponse = async (currentSpan)=>{
|
||||
var _cacheEntry_value;
|
||||
const responseGenerator = async ({ previousCacheEntry })=>{
|
||||
try {
|
||||
if (!isMinimalMode && isOnDemandRevalidate && revalidateOnlyGenerated && !previousCacheEntry) {
|
||||
res.statusCode = 404;
|
||||
// on-demand revalidate always sets this header
|
||||
res.setHeader('x-nextjs-cache', 'REVALIDATED');
|
||||
res.end('This page could not be found');
|
||||
return null;
|
||||
}
|
||||
const response = await invokeRouteModule(currentSpan);
|
||||
req.fetchMetrics = context.renderOpts.fetchMetrics;
|
||||
let pendingWaitUntil = context.renderOpts.pendingWaitUntil;
|
||||
// Attempt using provided waitUntil if available
|
||||
// if it's not we fallback to sendResponse's handling
|
||||
if (pendingWaitUntil) {
|
||||
if (ctx.waitUntil) {
|
||||
ctx.waitUntil(pendingWaitUntil);
|
||||
pendingWaitUntil = undefined;
|
||||
}
|
||||
}
|
||||
const cacheTags = context.renderOpts.collectedTags;
|
||||
// If the request is for a static response, we can cache it so long
|
||||
// as it's not edge.
|
||||
if (isIsr) {
|
||||
const blob = await response.blob();
|
||||
// Copy the headers from the response.
|
||||
const headers = toNodeOutgoingHttpHeaders(response.headers);
|
||||
if (cacheTags) {
|
||||
headers[NEXT_CACHE_TAGS_HEADER] = cacheTags;
|
||||
}
|
||||
if (!headers['content-type'] && blob.type) {
|
||||
headers['content-type'] = blob.type;
|
||||
}
|
||||
const revalidate = typeof context.renderOpts.collectedRevalidate === 'undefined' || context.renderOpts.collectedRevalidate >= INFINITE_CACHE ? false : context.renderOpts.collectedRevalidate;
|
||||
const expire = typeof context.renderOpts.collectedExpire === 'undefined' || context.renderOpts.collectedExpire >= INFINITE_CACHE ? undefined : context.renderOpts.collectedExpire;
|
||||
// Create the cache entry for the response.
|
||||
const cacheEntry = {
|
||||
value: {
|
||||
kind: CachedRouteKind.APP_ROUTE,
|
||||
status: response.status,
|
||||
body: Buffer.from(await blob.arrayBuffer()),
|
||||
headers
|
||||
},
|
||||
cacheControl: {
|
||||
revalidate,
|
||||
expire
|
||||
}
|
||||
};
|
||||
return cacheEntry;
|
||||
} else {
|
||||
// send response without caching if not ISR
|
||||
await sendResponse(nodeNextReq, nodeNextRes, response, context.renderOpts.pendingWaitUntil);
|
||||
return null;
|
||||
}
|
||||
} catch (err) {
|
||||
// if this is a background revalidate we need to report
|
||||
// the request error here as it won't be bubbled
|
||||
if (previousCacheEntry == null ? void 0 : previousCacheEntry.isStale) {
|
||||
const silenceLog = false;
|
||||
await routeModule.onRequestError(req, err, {
|
||||
routerKind: 'App Router',
|
||||
routePath: srcPage,
|
||||
routeType: 'route',
|
||||
revalidateReason: getRevalidateReason({
|
||||
isStaticGeneration,
|
||||
isOnDemandRevalidate
|
||||
})
|
||||
}, silenceLog, routerServerContext);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
const cacheEntry = await routeModule.handleResponse({
|
||||
req,
|
||||
nextConfig,
|
||||
cacheKey,
|
||||
routeKind: RouteKind.APP_ROUTE,
|
||||
isFallback: false,
|
||||
prerenderManifest,
|
||||
isRoutePPREnabled: false,
|
||||
isOnDemandRevalidate,
|
||||
revalidateOnlyGenerated,
|
||||
responseGenerator,
|
||||
waitUntil: ctx.waitUntil,
|
||||
isMinimalMode
|
||||
});
|
||||
// we don't create a cacheEntry for ISR
|
||||
if (!isIsr) {
|
||||
return null;
|
||||
}
|
||||
if ((cacheEntry == null ? void 0 : (_cacheEntry_value = cacheEntry.value) == null ? void 0 : _cacheEntry_value.kind) !== CachedRouteKind.APP_ROUTE) {
|
||||
var _cacheEntry_value1;
|
||||
throw Object.defineProperty(new Error(`Invariant: app-route received invalid cache entry ${cacheEntry == null ? void 0 : (_cacheEntry_value1 = cacheEntry.value) == null ? void 0 : _cacheEntry_value1.kind}`), "__NEXT_ERROR_CODE", {
|
||||
value: "E701",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
if (!isMinimalMode) {
|
||||
res.setHeader('x-nextjs-cache', isOnDemandRevalidate ? 'REVALIDATED' : cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT');
|
||||
}
|
||||
// Draft mode should never be cached
|
||||
if (isDraftMode) {
|
||||
res.setHeader('Cache-Control', 'private, no-cache, no-store, max-age=0, must-revalidate');
|
||||
}
|
||||
const headers = fromNodeOutgoingHttpHeaders(cacheEntry.value.headers);
|
||||
if (!(isMinimalMode && isIsr)) {
|
||||
headers.delete(NEXT_CACHE_TAGS_HEADER);
|
||||
}
|
||||
// If cache control is already set on the response we don't
|
||||
// override it to allow users to customize it via next.config
|
||||
if (cacheEntry.cacheControl && !res.getHeader('Cache-Control') && !headers.get('Cache-Control')) {
|
||||
headers.set('Cache-Control', getCacheControlHeader(cacheEntry.cacheControl));
|
||||
}
|
||||
await sendResponse(nodeNextReq, nodeNextRes, // @ts-expect-error - Argument of type 'Buffer<ArrayBufferLike>' is not assignable to parameter of type 'BodyInit | null | undefined'.
|
||||
new Response(cacheEntry.value.body, {
|
||||
headers,
|
||||
status: cacheEntry.value.status || 200
|
||||
}));
|
||||
return null;
|
||||
};
|
||||
// TODO: activeSpan code path is for when wrapped by
|
||||
// next-server can be removed when this is no longer used
|
||||
if (activeSpan) {
|
||||
await handleResponse(activeSpan);
|
||||
} else {
|
||||
await tracer.withPropagatedContext(req.headers, ()=>tracer.trace(BaseServerSpan.handleRequest, {
|
||||
spanName: `${method} ${srcPage}`,
|
||||
kind: SpanKind.SERVER,
|
||||
attributes: {
|
||||
'http.method': method,
|
||||
'http.target': req.url
|
||||
}
|
||||
}, handleResponse));
|
||||
}
|
||||
} catch (err) {
|
||||
if (!(err instanceof NoFallbackError)) {
|
||||
const silenceLog = false;
|
||||
await routeModule.onRequestError(req, err, {
|
||||
routerKind: 'App Router',
|
||||
routePath: normalizedSrcPage,
|
||||
routeType: 'route',
|
||||
revalidateReason: getRevalidateReason({
|
||||
isStaticGeneration,
|
||||
isOnDemandRevalidate
|
||||
})
|
||||
}, silenceLog, routerServerContext);
|
||||
}
|
||||
// rethrow so that we can handle serving error page
|
||||
// If this is during static generation, throw the error again.
|
||||
if (isIsr) throw err;
|
||||
// Otherwise, send a 500 response.
|
||||
await sendResponse(nodeNextReq, nodeNextRes, new Response(null, {
|
||||
status: 500
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=app-route.js.map
|
||||
Reference in New Issue
Block a user