Skip to content

Commit b5c6320

Browse files
committed
Refine filesystem output types
1 parent 6797e1e commit b5c6320

5 files changed

Lines changed: 173 additions & 107 deletions

File tree

packages/next/errors.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1303,5 +1303,6 @@
13031303
"1302": "Attempted to advance to stage %s but the render is limited to %s",
13041304
"1303": "Route \"%s\": A Client Component used \\`%s\\` outside of \\`<Suspense>\\`.\\n\\nThis prevents the route from being prerendered because the value is only available at runtime.\\n\\nWays to fix this:\\n - [stream] Wrap the Client Component in \\`<Suspense fallback={...}>\\`\\n https://nextjs.org/docs/messages/next-prerender-client-hook#wrap-the-client-component-in-suspense",
13051305
"1304": "Route \"%s\": A Client Component used \\`%s\\` outside of \\`<Suspense>\\`.\\n\\nThis prevents the route from being prerendered because the value is only available at runtime.\\n\\nWays to fix this:\\n - [stream] Wrap the Client Component in \\`<Suspense fallback={...}>\\`\\n https://nextjs.org/docs/messages/next-prerender-client-hook#wrap-the-client-component-in-suspense\\n - [prerender] If the dynamic params are known, prerender them with \\`generateStaticParams\\`\\n https://nextjs.org/docs/messages/next-prerender-client-hook#prerender-known-dynamic-params",
1306-
"1305": "\\`partialPrefetching\\` requires \\`cacheComponents\\` to be enabled. Please update your %s accordingly."
1306+
"1305": "\\`partialPrefetching\\` requires \\`cacheComponents\\` to be enabled. Please update your %s accordingly.",
1307+
"1306": "Invariant: dev virtual filesystem item was not handled by the development bundler"
13071308
}

packages/next/src/server/lib/router-server.ts

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,11 @@ export async function initialize(opts: {
544544
)
545545
}
546546

547-
if (matchedOutput?.fsPath && matchedOutput.itemPath) {
547+
if (
548+
matchedOutput?.type === 'nextStaticFolder' ||
549+
matchedOutput?.type === 'legacyStaticFolder' ||
550+
matchedOutput?.type === 'publicFolder'
551+
) {
548552
if (
549553
opts.dev &&
550554
(fsChecker.appFiles.has(matchedOutput.itemPath) ||
@@ -652,39 +656,57 @@ export async function initialize(opts: {
652656
if (matchedOutput) {
653657
invokedOutputs.add(matchedOutput.itemPath)
654658

655-
// fsChecker preserves compilation errors from its dev ensure step so
656-
// the route remains matched. Log the compiler diagnostic, then render
657-
// the matched route as a 500 instead of falling through to a 404.
658-
if (matchedOutput.error && development) {
659-
development.bundler.logErrorWithOriginalStack(
660-
matchedOutput.error,
661-
matchedOutput.type === 'appFile' ? 'app-dir' : undefined
659+
if (
660+
matchedOutput.type === 'appFile' ||
661+
matchedOutput.type === 'pageFile'
662+
) {
663+
// fsChecker preserves compilation errors from its dev ensure step so
664+
// the route remains matched. Log the compiler diagnostic, then render
665+
// the matched route as a 500 instead of falling through to a 404.
666+
if (matchedOutput.error && development) {
667+
development.bundler.logErrorWithOriginalStack(
668+
matchedOutput.error,
669+
matchedOutput.type === 'appFile' ? 'app-dir' : undefined
670+
)
671+
}
672+
673+
return await invokeRender(
674+
parsedUrl,
675+
parsedUrl.pathname || '/',
676+
handleIndex,
677+
{
678+
invokeOutput: matchedOutput.itemPath,
679+
...(matchedOutput.error
680+
? {
681+
invokeStatus: 500,
682+
invokeError: matchedOutput.error,
683+
}
684+
: undefined),
685+
// fsChecker owns the route match for filesystem requests. Forward
686+
// it so BaseServer does not need the removed matcher manager.
687+
match: {
688+
definition: matchedOutput.route,
689+
params: matchedOutput.params,
690+
},
691+
}
662692
)
663693
}
664694

665-
return await invokeRender(
666-
parsedUrl,
667-
parsedUrl.pathname || '/',
668-
handleIndex,
669-
{
670-
invokeOutput: matchedOutput.itemPath,
671-
...(matchedOutput.error
672-
? {
673-
invokeStatus: 500,
674-
invokeError: matchedOutput.error,
675-
}
676-
: undefined),
677-
// fsChecker owns the route match for filesystem requests. Forward
678-
// it so BaseServer does not need the removed matcher manager.
679-
...(matchedOutput.route
680-
? {
681-
match: {
682-
definition: matchedOutput.route,
683-
params: matchedOutput.params,
684-
},
685-
}
686-
: undefined),
687-
}
695+
if (matchedOutput.type === 'nextImage') {
696+
// Image optimization is handled by NextNodeServer, so forward the
697+
// request to the render server without a page route match.
698+
return await invokeRender(
699+
parsedUrl,
700+
parsedUrl.pathname || '/',
701+
handleIndex,
702+
{
703+
invokeOutput: matchedOutput.itemPath,
704+
}
705+
)
706+
}
707+
708+
throw new Error(
709+
'Invariant: dev virtual filesystem item was not handled by the development bundler'
688710
)
689711
}
690712

packages/next/src/server/lib/router-utils/filesystem.ts

Lines changed: 92 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,50 @@ import { isAppPageRouteDefinition } from '../../route-definitions/app-page-route
6464
import { compareAppPaths } from '../../../shared/lib/router/utils/app-paths'
6565
import { normalizeCatchAllRoutes } from './normalize-catchall-routes'
6666

67-
export type FsOutput = {
68-
type:
69-
| 'appFile'
70-
| 'pageFile'
71-
| 'nextImage'
72-
| 'publicFolder'
73-
| 'nextStaticFolder'
74-
| 'legacyStaticFolder'
75-
| 'devVirtualFsItem'
76-
67+
type BaseRouteFsOutput = {
7768
itemPath: string
78-
fsPath?: string
79-
itemsRoot?: string
8069
locale?: string
81-
route?: RouteDefinition
70+
route: RouteDefinition
8271
params?: Params
83-
requestPath?: string
8472
error?: Error
8573
}
8674

75+
type AppFileFsOutput = BaseRouteFsOutput & {
76+
type: 'appFile'
77+
}
78+
79+
type PageFileFsOutput = BaseRouteFsOutput & {
80+
type: 'pageFile'
81+
}
82+
83+
type StaticFsOutput = {
84+
type: 'publicFolder' | 'nextStaticFolder' | 'legacyStaticFolder'
85+
itemPath: string
86+
fsPath: string
87+
itemsRoot: string
88+
}
89+
90+
type NextImageFsOutput = {
91+
type: 'nextImage'
92+
itemPath: string
93+
}
94+
95+
type DevVirtualFsOutput = {
96+
type: 'devVirtualFsItem'
97+
itemPath: string
98+
}
99+
100+
export type FsOutput =
101+
| AppFileFsOutput
102+
| PageFileFsOutput
103+
| StaticFsOutput
104+
| NextImageFsOutput
105+
| DevVirtualFsOutput
106+
107+
type FsOutputEnsure = (AppFileFsOutput | PageFileFsOutput) & {
108+
requestPath?: string
109+
}
110+
87111
type FilesystemRouteDefinition = RouteDefinition & {
88112
i18n?: {
89113
locale?: string
@@ -181,7 +205,7 @@ export async function setupFsCheck(opts: {
181205
return 1
182206
}
183207
return (
184-
(value.fsPath || '').length +
208+
('fsPath' in value ? value.fsPath.length : 0) +
185209
value.itemPath.length +
186210
value.type.length
187211
)
@@ -625,7 +649,7 @@ export async function setupFsCheck(opts: {
625649
debug('pageFiles', pageFiles)
626650
debug('appFiles', appFiles)
627651

628-
let ensureFn: (item: FsOutput) => Promise<void> | undefined
652+
let ensureFn: (item: FsOutputEnsure) => Promise<void> | undefined
629653

630654
const normalizers = {
631655
// Because we can't know if the app directory is enabled or not at this
@@ -733,12 +757,15 @@ export async function setupFsCheck(opts: {
733757
// "nextStaticFolder" sets Cache-Control "no-store" on dev.
734758
type: 'nextStaticFolder',
735759
fsPath,
736-
itemPath: fsPath,
760+
itemPath: path.basename(fsPath),
761+
itemsRoot: path.dirname(fsPath),
737762
}
738763
}
739764
}
740765

741-
const itemsToCheck: Array<[Set<string>, FsOutput['type']]> = [
766+
const itemsToCheck: Array<
767+
[Set<string>, Exclude<FsOutput['type'], 'nextImage'>]
768+
> = [
742769
[this.devVirtualFsItems, 'devVirtualFsItem'],
743770
[nextStaticFolderItems, 'nextStaticFolder'],
744771
[legacyStaticFolderItems, 'legacyStaticFolder'],
@@ -871,7 +898,6 @@ export async function setupFsCheck(opts: {
871898
}
872899
case 'appFile':
873900
case 'pageFile':
874-
case 'nextImage':
875901
case 'devVirtualFsItem': {
876902
break
877903
}
@@ -919,11 +945,7 @@ export async function setupFsCheck(opts: {
919945

920946
let error: Error | undefined
921947

922-
if (opts.dev && isPageOrAppFile) {
923-
if (!route) {
924-
continue
925-
}
926-
948+
if (opts.dev && isPageOrAppFile && route) {
927949
const isAppFile = type === 'appFile'
928950

929951
// Attempt to ensure the page/app file is compiled and ready.
@@ -956,40 +978,58 @@ export async function setupFsCheck(opts: {
956978
continue
957979
}
958980

959-
if (isPageOrAppFile && !route && !matchedItem) {
960-
continue
961-
}
981+
let itemResult: FsOutput
962982

963-
let params: Params | undefined
964-
if (route && isAppPageRouteDefinition(route)) {
965-
// Parallel app routes can contribute multiple dynamic app paths
966-
// to one pathname. Preserve the params from the matching path.
967-
for (const appPath of route.appPaths) {
968-
const routePathname = appNormalizers.pathname.normalize(appPath)
969-
if (!isDynamicRoute(routePathname)) {
970-
continue
971-
}
983+
if (isPageOrAppFile) {
984+
if (!route) {
985+
continue
986+
}
987+
988+
let params: Params | undefined
989+
if (isAppPageRouteDefinition(route)) {
990+
// Parallel app routes can contribute multiple dynamic app paths
991+
// to one pathname. Preserve the params from the matching path.
992+
for (const appPath of route.appPaths) {
993+
const routePathname = appNormalizers.pathname.normalize(appPath)
994+
if (!isDynamicRoute(routePathname)) {
995+
continue
996+
}
972997

973-
const routeParams = getRouteMatcher(getRouteRegex(routePathname))(
974-
curItemPath
975-
)
998+
const routeParams = getRouteMatcher(
999+
getRouteRegex(routePathname)
1000+
)(curItemPath)
9761001

977-
if (routeParams) {
978-
params = routeParams
979-
break
1002+
if (routeParams) {
1003+
params = routeParams
1004+
break
1005+
}
9801006
}
9811007
}
982-
}
9831008

984-
const itemResult = {
985-
type,
986-
fsPath,
987-
locale,
988-
itemsRoot,
989-
itemPath: curItemPath,
990-
route,
991-
params,
992-
error,
1009+
itemResult = {
1010+
type,
1011+
locale,
1012+
itemPath: curItemPath,
1013+
route,
1014+
params,
1015+
error,
1016+
}
1017+
} else if (type === 'devVirtualFsItem') {
1018+
itemResult = {
1019+
type,
1020+
itemPath: curItemPath,
1021+
}
1022+
} else {
1023+
if (!fsPath || !itemsRoot) {
1024+
continue
1025+
}
1026+
1027+
itemResult = {
1028+
type,
1029+
fsPath,
1030+
itemsRoot,
1031+
itemPath: curItemPath,
1032+
}
9931033
}
9941034

9951035
getItemsLru?.set(itemKey, itemResult)

packages/next/src/server/lib/router-utils/resolve-routes.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -326,27 +326,32 @@ export function getResolveRoutes(
326326
curPathname || undefined
327327
)
328328

329+
if (
330+
pageOutput?.type !== 'appFile' &&
331+
pageOutput?.type !== 'pageFile'
332+
) {
333+
continue
334+
}
335+
329336
// i18n locales aren't matched for app dir
330337
if (
331-
pageOutput?.type === 'appFile' &&
338+
pageOutput.type === 'appFile' &&
332339
initialLocaleResult?.detectedLocale
333340
) {
334341
continue
335342
}
336343

337-
if (pageOutput && curPathname?.startsWith('/_next/data')) {
344+
if (curPathname?.startsWith('/_next/data')) {
338345
setIsNextDataRequest()
339346
}
340347

341348
if (config.useFileSystemPublicRoutes || didRewrite) {
342-
return pageOutput
343-
? {
344-
...pageOutput,
345-
// The dynamic-route scan matched the concrete request path;
346-
// keep those params with the fsChecker route definition.
347-
params,
348-
}
349-
: null
349+
return {
350+
...pageOutput,
351+
// The dynamic-route scan matched the concrete request path;
352+
// keep those params with the fsChecker route definition.
353+
params,
354+
}
350355
}
351356
}
352357
}
@@ -519,7 +524,7 @@ export function getResolveRoutes(
519524
) {
520525
matchedOutput = output
521526

522-
if (output.locale) {
527+
if ('locale' in output && output.locale) {
523528
addRequestMeta(req, 'locale', output.locale)
524529
}
525530

packages/next/src/server/lib/router-utils/setup-dev-bundler.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -364,18 +364,16 @@ async function startWatcher(
364364
}
365365

366366
opts.fsChecker.ensureCallback(async function ensure(item) {
367-
if (item.type === 'appFile' || item.type === 'pageFile') {
368-
const definition = item.route
369-
// App paths can normalize away groups and parallel slots. Ensure the
370-
// concrete route definition rather than the normalized request pathname.
371-
await hotReloader.ensurePage({
372-
clientOnly: false,
373-
page: definition?.page ?? item.itemPath,
374-
isApp: item.type === 'appFile',
375-
definition,
376-
url: item.requestPath,
377-
})
378-
}
367+
const definition = item.route
368+
// App paths can normalize away groups and parallel slots. Ensure the
369+
// concrete route definition rather than the normalized request pathname.
370+
await hotReloader.ensurePage({
371+
clientOnly: false,
372+
page: definition.page,
373+
isApp: item.type === 'appFile',
374+
definition,
375+
url: item.requestPath,
376+
})
379377
})
380378

381379
let resolved = false

0 commit comments

Comments
 (0)