Skip to content

Commit

Permalink
Move middleware declaration to the project root
Browse files Browse the repository at this point in the history
  • Loading branch information
javivelasco committed May 18, 2022
1 parent 828804e commit ff8dfc6
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 89 deletions.
5 changes: 2 additions & 3 deletions errors/nested-middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#### Why This Error Occurred

You are defining a middleware file in a location different from `pages/_middleware` which is _deprecated_.
You are defining a middleware file in a location different from `<root>/middleware` which is _deprecated_.

Declaring a middleware file under specific pages implied that it would _only_ be executed when pages below its declaration were matched.
This execution model allowed the nesting of multiple middleware, which is hard to reason about and led to consequences such as dragging effects between different middleware executions.
Expand All @@ -11,8 +11,7 @@ The API has been _deprecated_ in favor of a simpler model with a single root mid

#### Possible Ways to Fix It

To fix this error, declare your middleware in the root pages folder and use `NextRequest` parsed URL to define which path the middleware code should be executed for.
For example, a middleware declared under `pages/about/_middleware.js` can be moved to `pages/_middleware`. A conditional can be used to ensure the middleware executes only when it matches the `about/*` path:
To fix this error, declare your middleware in the root folder and use `NextRequest` parsed URL to define which path the middleware code should be executed for. For example, a middleware declared under `pages/about/_middleware.js` can be moved to `middleware`. A conditional can be used to ensure the middleware executes only when it matches the `about/*` path:

```typescript
import type { NextRequest } from 'next/server'
Expand Down
66 changes: 47 additions & 19 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { stringify } from 'querystring'
import {
API_ROUTE,
DOT_NEXT_ALIAS,
MIDDLEWARE_FILE,
MIDDLEWARE_FILENAME,
PAGES_DIR_ALIAS,
ROOT_DIR_ALIAS,
VIEWS_DIR_ALIAS,
} from '../lib/constants'
import {
Expand All @@ -23,7 +26,6 @@ import {
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
EDGE_RUNTIME_WEBPACK,
} from '../shared/lib/constants'
import { MIDDLEWARE_ROUTE } from '../lib/constants'
import { __ApiPreviewProps } from '../server/api-utils'
import { isTargetLikeServerless } from '../server/utils'
import { warn } from './output/log'
Expand Down Expand Up @@ -57,18 +59,17 @@ export function getPageFromPath(pagePath: string, pageExtensions: string[]) {
export function createPagesMapping({
hasServerComponents,
isDev,
isViews,
pageExtensions,
pagePaths,
pagesType,
}: {
hasServerComponents: boolean
isDev: boolean
isViews?: boolean
pageExtensions: string[]
pagePaths: string[]
pagesType: 'pages' | 'root' | 'views'
}): { [page: string]: string } {
const previousPages: { [key: string]: string } = {}
const pathAlias = isViews ? VIEWS_DIR_ALIAS : PAGES_DIR_ALIAS
const pages = pagePaths.reduce<{ [key: string]: string }>(
(result, pagePath) => {
// Do not process .d.ts files inside the `pages` folder
Expand Down Expand Up @@ -97,13 +98,22 @@ export function createPagesMapping({
previousPages[pageKey] = pagePath
}

result[pageKey] = normalizePathSep(join(pathAlias, pagePath))
result[pageKey] = normalizePathSep(
join(
pagesType === 'pages'
? PAGES_DIR_ALIAS
: pagesType === 'views'
? VIEWS_DIR_ALIAS
: ROOT_DIR_ALIAS,
pagePath
)
)
return result
},
{}
)

if (isViews) {
if (pagesType !== 'pages') {
return pages
}

Expand Down Expand Up @@ -259,6 +269,8 @@ interface CreateEntrypointsParams {
pages: { [page: string]: string }
pagesDir: string
previewMode: __ApiPreviewProps
rootDir: string
rootPaths?: Record<string, string>
target: 'server' | 'serverless' | 'experimental-serverless-trace'
viewsDir?: string
viewPaths?: Record<string, string>
Expand All @@ -275,7 +287,7 @@ export function getEdgeServerEntry(opts: {
page: string
pages: { [page: string]: string }
}) {
if (opts.page.match(MIDDLEWARE_ROUTE)) {
if (opts.page === MIDDLEWARE_FILE) {
const loaderParams: MiddlewareLoaderOptions = {
absolutePagePath: opts.absolutePagePath,
page: opts.page,
Expand Down Expand Up @@ -388,6 +400,8 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
pages,
pagesDir,
isDev,
rootDir,
rootPaths,
target,
viewsDir,
viewPaths,
Expand All @@ -398,14 +412,16 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
const client: webpack5.EntryObject = {}

const getEntryHandler =
(mappings: Record<string, string>, isViews: boolean) =>
(mappings: Record<string, string>, pagesType: 'views' | 'pages' | 'root') =>
async (page: string) => {
const bundleFile = normalizePagePath(page)
const clientBundlePath = posix.join('pages', bundleFile)
const serverBundlePath = posix.join(
isViews ? 'views' : 'pages',
bundleFile
)
const serverBundlePath =
pagesType === 'pages'
? posix.join('pages', bundleFile)
: pagesType === 'views'
? posix.join('views', bundleFile)
: bundleFile.slice(1)
const absolutePagePath = mappings[page]

// Handle paths that have aliases
Expand All @@ -418,15 +434,22 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
return absolutePagePath.replace(VIEWS_DIR_ALIAS, viewsDir)
}

if (absolutePagePath.startsWith(ROOT_DIR_ALIAS)) {
return absolutePagePath.replace(ROOT_DIR_ALIAS, rootDir)
}

return require.resolve(absolutePagePath)
})()

/**
* When we find a middleware that is declared in the pages/ root we fail.
* When we find a middleware file that is not in the ROOT_DIR we fail.
* There is no need to check on `dev` as this should only happen when
* building for production.
*/
if (/[\\\\/]_middleware$/.test(page) && page !== '/_middleware') {
if (
!absolutePagePath.startsWith(ROOT_DIR_ALIAS) &&
/[\\\\/]_middleware$/.test(page)
) {
throw new Error(
`nested Middleware is deprecated (found pages${page}) - https://nextjs.org/docs/messages/nested-middleware`
)
Expand All @@ -450,7 +473,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
}
},
onServer: () => {
if (isViews && viewsDir) {
if (pagesType === 'views' && viewsDir) {
server[serverBundlePath] = getViewsEntry({
name: serverBundlePath,
pagePath: mappings[page],
Expand Down Expand Up @@ -488,10 +511,15 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
}

if (viewsDir && viewPaths) {
const entryHandler = getEntryHandler(viewPaths, true)
const entryHandler = getEntryHandler(viewPaths, 'views')
await Promise.all(Object.keys(viewPaths).map(entryHandler))
}
await Promise.all(Object.keys(pages).map(getEntryHandler(pages, false)))
if (rootPaths) {
await Promise.all(
Object.keys(rootPaths).map(getEntryHandler(rootPaths, 'root'))
)
}
await Promise.all(Object.keys(pages).map(getEntryHandler(pages, 'pages')))

return {
client,
Expand All @@ -507,7 +535,7 @@ export function runDependingOnPageType<T>(params: {
page: string
pageRuntime: PageRuntime
}) {
if (params.page.match(MIDDLEWARE_ROUTE)) {
if (params.page === MIDDLEWARE_FILE) {
return [params.onEdgeServer()]
} else if (params.page.match(API_ROUTE)) {
return [params.onServer()]
Expand Down Expand Up @@ -556,7 +584,7 @@ export function finalizeEntrypoint({

if (compilerType === 'edge-server') {
return {
layer: MIDDLEWARE_ROUTE.test(name) ? 'middleware' : undefined,
layer: name === MIDDLEWARE_FILENAME ? 'middleware' : undefined,
library: { name: ['_ENTRIES', `middleware_[name]`], type: 'assign' },
runtime: EDGE_RUNTIME_WEBPACK,
asyncChunks: false,
Expand Down
50 changes: 31 additions & 19 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-me
import {
STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR,
PUBLIC_DIR_MIDDLEWARE_CONFLICT,
MIDDLEWARE_ROUTE,
MIDDLEWARE_FILENAME,
MIDDLEWARE_FILE,
PAGES_DIR_ALIAS,
} from '../lib/constants'
import { fileExists } from '../lib/file-exists'
Expand Down Expand Up @@ -112,6 +113,7 @@ import { recursiveReadDir } from '../lib/recursive-readdir'
import { lockfilePatchPromise, teardownTraceSubscriber } from './swc'
import { injectedClientEntries } from './webpack/plugins/flight-manifest-plugin'
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'
import { flatReaddir } from '../lib/flat-readdir'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand Down Expand Up @@ -323,6 +325,13 @@ export default async function build(
)
}

const rootPaths = await flatReaddir(
dir,
new RegExp(
`^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`
)
)

// needed for static exporting since we want to replace with HTML
// files

Expand All @@ -342,11 +351,12 @@ export default async function build(
hasServerComponents,
isDev: false,
pageExtensions: config.pageExtensions,
pagePaths,
pagesType: 'pages',
pagePaths: pagePaths,
})
)

let mappedViewPaths: ReturnType<typeof createPagesMapping> | undefined
let mappedViewPaths: { [page: string]: string } | undefined

if (viewPaths && viewsDir) {
mappedViewPaths = nextBuildSpan
Expand All @@ -356,12 +366,23 @@ export default async function build(
pagePaths: viewPaths!,
hasServerComponents,
isDev: false,
isViews: true,
pagesType: 'views',
pageExtensions: config.pageExtensions,
})
)
}

let mappedRootPaths: { [page: string]: string } = {}
if (rootPaths.length > 0) {
mappedRootPaths = createPagesMapping({
hasServerComponents,
isDev: false,
pageExtensions: config.pageExtensions,
pagePaths: rootPaths,
pagesType: 'root',
})
}

const entrypoints = await nextBuildSpan
.traceChild('create-entrypoints')
.traceAsyncFn(() =>
Expand All @@ -374,6 +395,8 @@ export default async function build(
pagesDir,
previewMode: previewProps,
target,
rootDir: dir,
rootPaths: mappedRootPaths,
viewsDir,
viewPaths: mappedViewPaths,
pageExtensions: config.pageExtensions,
Expand All @@ -386,7 +409,7 @@ export default async function build(
const hasCustomErrorPage =
mappedPages['/_error'].startsWith(PAGES_DIR_ALIAS)

if (pageKeys.some((page) => MIDDLEWARE_ROUTE.test(page))) {
if (mappedRootPaths?.[MIDDLEWARE_FILE]) {
Log.warn(
`using beta Middleware (not covered by semver) - https://nextjs.org/docs/messages/beta-middleware`
)
Expand Down Expand Up @@ -535,17 +558,10 @@ export default async function build(
redirects: redirects.map((r: any) => buildCustomRoute(r, 'redirect')),
headers: headers.map((r: any) => buildCustomRoute(r, 'header')),
dynamicRoutes: getSortedRoutes(pageKeys)
.filter(
(page) => isDynamicRoute(page) && !page.match(MIDDLEWARE_ROUTE)
)
.filter(isDynamicRoute)
.map(pageToRoute),
staticRoutes: getSortedRoutes(pageKeys)
.filter(
(page) =>
!isDynamicRoute(page) &&
!page.match(MIDDLEWARE_ROUTE) &&
!isReservedPage(page)
)
.filter((page) => !isDynamicRoute(page) && !isReservedPage(page))
.map(pageToRoute),
dataRoutes: [],
i18n: config.i18n || undefined,
Expand Down Expand Up @@ -1066,7 +1082,6 @@ export default async function build(
let isServerComponent = false
let isHybridAmp = false
let ssgPageRoutes: string[] | null = null
let isMiddlewareRoute = !!page.match(MIDDLEWARE_ROUTE)

const pagePath = pagePaths.find(
(p) =>
Expand All @@ -1085,7 +1100,6 @@ export default async function build(
}

if (
!isMiddlewareRoute &&
!isReservedPage(page) &&
// We currently don't support static optimization in the Edge runtime.
pageRuntime !== 'edge'
Expand Down Expand Up @@ -2090,9 +2104,7 @@ export default async function build(
rewritesWithHasCount: combinedRewrites.filter((r: any) => !!r.has)
.length,
redirectsWithHasCount: redirects.filter((r: any) => !!r.has).length,
middlewareCount: pageKeys.filter((page) =>
MIDDLEWARE_ROUTE.test(page)
).length,
middlewareCount: Object.keys(rootPaths).length > 0 ? 1 : 0,
})
)

Expand Down
14 changes: 6 additions & 8 deletions packages/next/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
SSG_GET_INITIAL_PROPS_CONFLICT,
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
SERVER_PROPS_SSG_CONFLICT,
MIDDLEWARE_ROUTE,
MIDDLEWARE_FILENAME,
} from '../lib/constants'
import { EDGE_RUNTIME_WEBPACK } from '../shared/lib/constants'
import prettyBytes from '../lib/pretty-bytes'
Expand Down Expand Up @@ -1183,12 +1183,9 @@ export async function copyTracedFiles(
)
}

for (const page of pageKeys) {
if (MIDDLEWARE_ROUTE.test(page)) {
const { files } =
middlewareManifest.middleware[page.replace(/\/_middleware$/, '') || '/']

for (const file of files) {
for (const middleware of Object.values(middlewareManifest.middleware) || []) {
if (middleware.name === MIDDLEWARE_FILENAME) {
for (const file of middleware.files) {
const originalPath = path.join(distDir, file)
const fileOutputPath = path.join(
outputPath,
Expand All @@ -1198,9 +1195,10 @@ export async function copyTracedFiles(
await fs.mkdir(path.dirname(fileOutputPath), { recursive: true })
await fs.copyFile(originalPath, fileOutputPath)
}
continue
}
}

for (const page of pageKeys) {
const pageFile = path.join(
distDir,
'server',
Expand Down
2 changes: 2 additions & 0 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
NEXT_PROJECT_ROOT,
NEXT_PROJECT_ROOT_DIST_CLIENT,
PAGES_DIR_ALIAS,
ROOT_DIR_ALIAS,
VIEWS_DIR_ALIAS,
} from '../lib/constants'
import { fileExists } from '../lib/file-exists'
Expand Down Expand Up @@ -651,6 +652,7 @@ export default async function getBaseWebpackConfig(
[VIEWS_DIR_ALIAS]: viewsDir,
}
: {}),
[ROOT_DIR_ALIAS]: dir,
[DOT_NEXT_ALIAS]: distDir,
...(isClient || isEdgeServer ? getOptimizedAliases() : {}),
...getReactProfilingInProduction(),
Expand Down
Loading

0 comments on commit ff8dfc6

Please sign in to comment.