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
12 changes: 7 additions & 5 deletions crates/next-core/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::util::load_next_js_template;
#[turbo_tasks::function]
pub async fn middleware_files(page_extensions: Vc<Vec<RcStr>>) -> Result<Vc<Vec<RcStr>>> {
let extensions = page_extensions.await?;
let files = ["middleware.", "src/middleware."]
let files = ["middleware.", "src/middleware.", "proxy.", "src/proxy."]
.into_iter()
.flat_map(|f| {
extensions
Expand All @@ -29,14 +29,16 @@ pub async fn get_middleware_module(
) -> Result<Vc<Box<dyn Module>>> {
const INNER: &str = "INNER_MIDDLEWARE_MODULE";

// Determine if this is a proxy file by checking the module path
let userland_path = userland_module.ident().path().await?.to_string();
let is_proxy = userland_path.contains("proxy.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let page_path = if is_proxy { "/proxy" } else { "/middleware" };

// Load the file from the next.js codebase.
let source = load_next_js_template(
"middleware.js",
project_root,
&[
("VAR_USERLAND", INNER),
("VAR_DEFINITION_PAGE", "/middleware"),
],
&[("VAR_USERLAND", INNER), ("VAR_DEFINITION_PAGE", page_path)],
&[],
&[],
)
Expand Down
4 changes: 3 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -818,5 +818,7 @@
"817": "`cacheLifeProfiles` should always be provided.",
"818": "`cacheLife()` can only be called inside a \"use cache\" function.",
"819": "`cacheTag()` can only be called inside a \"use cache\" function.",
"820": "`cacheLife()` can only be called during App Router rendering at the moment."
"820": "`cacheLife()` can only be called during App Router rendering at the moment.",
"821": "Both \"%s\" and \"%s\" files are detected. Please use \"%s\" instead.",
"822": "The %s \"%s\" must export a %s or a \\`default\\` function"
}
36 changes: 21 additions & 15 deletions packages/next/src/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,12 @@ export async function createEntrypoints(
isDev: false,
})
} else if (isMiddlewareFile(page)) {
server[serverBundlePath.replace('src/', '')] = getEdgeServerEntry({
server[
serverBundlePath
// proxy.js still uses middleware.js for bundle path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// proxy.js still uses middleware.js for bundle path
// proxy.js still uses middleware.js for bundle path for now (revisit when removing middleware.js fully)

.replace('proxy', 'middleware')
.replace('src/', '')
] = getEdgeServerEntry({
...params,
rootDir,
absolutePagePath: absolutePagePath,
Expand Down Expand Up @@ -1023,20 +1028,21 @@ export async function createEntrypoints(
: undefined,
}).import
}
edgeServer[serverBundlePath] = getEdgeServerEntry({
...params,
rootDir,
absolutePagePath: absolutePagePath,
bundlePath: clientBundlePath,
isDev: false,
isServerComponent,
page,
middleware: staticInfo?.middleware,
pagesType,
appDirLoader,
preferredRegion: staticInfo.preferredRegion,
middlewareConfig: staticInfo.middleware,
})
edgeServer[serverBundlePath.replace('proxy', 'middleware')] =
getEdgeServerEntry({
...params,
rootDir,
absolutePagePath: absolutePagePath,
bundlePath: clientBundlePath,
isDev: false,
isServerComponent,
page,
middleware: staticInfo?.middleware,
pagesType,
appDirLoader,
preferredRegion: staticInfo.preferredRegion,
middlewareConfig: staticInfo.middleware,
})
}
},
})
Expand Down
23 changes: 20 additions & 3 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR,
PUBLIC_DIR_MIDDLEWARE_CONFLICT,
MIDDLEWARE_FILENAME,
PROXY_FILENAME,
PAGES_DIR_ALIAS,
INSTRUMENTATION_HOOK_FILENAME,
RSC_PREFETCH_SUFFIX,
Expand Down Expand Up @@ -1160,6 +1161,10 @@ export default async function build(
`^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`
)

const proxyDetectionRegExp = new RegExp(
`^${PROXY_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`
)

const instrumentationHookDetectionRegExp = new RegExp(
`^${INSTRUMENTATION_HOOK_FILENAME}\\.(?:${config.pageExtensions.join(
'|'
Expand All @@ -1169,6 +1174,7 @@ export default async function build(
const rootDir = path.join((pagesDir || appDir)!, '..')
const includes = [
middlewareDetectionRegExp,
proxyDetectionRegExp,
instrumentationHookDetectionRegExp,
]

Expand All @@ -1183,6 +1189,17 @@ export default async function build(
const hasMiddlewareFile = rootPaths.some((p) =>
p.includes(MIDDLEWARE_FILENAME)
)
const hasProxyFile = rootPaths.some((p) => p.includes(PROXY_FILENAME))
if (hasMiddlewareFile) {
if (hasProxyFile) {
throw new Error(
`Both "${MIDDLEWARE_FILENAME}" and "${PROXY_FILENAME}" files are detected. Please use "${PROXY_FILENAME}" instead.`
)
}
Log.warn(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Log.warn(
// TODO: drop support for `middleware.js` name before v17
Log.warn(

`The "${MIDDLEWARE_FILENAME}" file convention is deprecated. Please use "${PROXY_FILENAME}" instead.`
)
}

NextBuildContext.hasInstrumentationHook = hasInstrumentationHook

Expand Down Expand Up @@ -2567,8 +2584,8 @@ export default async function build(
)
}

const middlewareFile = rootPaths.find((p) =>
p.includes(MIDDLEWARE_FILENAME)
const middlewareFile = rootPaths.find(
(p) => p.includes(MIDDLEWARE_FILENAME) || p.includes(PROXY_FILENAME)
)
let hasNodeMiddleware = false

Expand Down Expand Up @@ -3943,7 +3960,7 @@ export default async function build(
rewritesWithHasCount: combinedRewrites.filter((r: any) => !!r.has)
.length,
redirectsWithHasCount: redirects.filter((r: any) => !!r.has).length,
middlewareCount: hasMiddlewareFile ? 1 : 0,
middlewareCount: hasMiddlewareFile || hasProxyFile ? 1 : 0,
totalAppPagesCount,
staticAppPagesCount,
serverAppPagesCount,
Expand Down
6 changes: 4 additions & 2 deletions packages/next/src/build/templates/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { edgeInstrumentationOnRequestError } from '../../server/web/globals'
import { isNextRouterError } from '../../client/components/is-next-router-error'

const mod = { ..._mod }
const handler = mod.middleware || mod.default

const page = 'VAR_DEFINITION_PAGE'
// @ts-expect-error `page` will be replaced during build
const isProxy = page === '/proxy' || page === '/src/proxy'
const handler = (isProxy ? mod.proxy : mod.middleware) || mod.default

if (typeof handler !== 'function') {
throw new Error(
`The Middleware "${page}" must export a \`middleware\` or a \`default\` function`
`The ${isProxy ? 'Proxy' : 'Middleware'} "${page}" must export a ${isProxy ? '`proxy`' : '`middleware`'} or a \`default\` function`
)
}

Expand Down
22 changes: 16 additions & 6 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
SERVER_PROPS_SSG_CONFLICT,
MIDDLEWARE_FILENAME,
PROXY_FILENAME,
INSTRUMENTATION_HOOK_FILENAME,
WEBPACK_LAYERS,
} from '../lib/constants'
Expand Down Expand Up @@ -95,7 +96,12 @@ export function difference<T>(
}

export function isMiddlewareFilename(file?: string | null) {
return file === MIDDLEWARE_FILENAME || file === `src/${MIDDLEWARE_FILENAME}`
return (
file === MIDDLEWARE_FILENAME ||
file === `src/${MIDDLEWARE_FILENAME}` ||
file === PROXY_FILENAME ||
file === `src/${PROXY_FILENAME}`
)
}

export function isInstrumentationHookFilename(file?: string | null) {
Expand Down Expand Up @@ -428,7 +434,7 @@ export async function printTreeView(
const middlewareInfo = middlewareManifest.middleware?.['/']
if (middlewareInfo?.files.length > 0) {
messages.push([])
messages.push(['ƒ Middleware'])
messages.push([Proxy (Middleware)'])
}

print(
Expand Down Expand Up @@ -1327,7 +1333,10 @@ export function isCustomErrorPage(page: string) {

export function isMiddlewareFile(file: string) {
return (
file === `/${MIDDLEWARE_FILENAME}` || file === `/src/${MIDDLEWARE_FILENAME}`
file === `/${MIDDLEWARE_FILENAME}` ||
file === `/src/${MIDDLEWARE_FILENAME}` ||
file === `/${PROXY_FILENAME}` ||
file === `/src/${PROXY_FILENAME}`
)
}

Expand Down Expand Up @@ -1357,9 +1366,10 @@ export function getPossibleMiddlewareFilenames(
folder: string,
extensions: string[]
) {
return extensions.map((extension) =>
path.join(folder, `${MIDDLEWARE_FILENAME}.${extension}`)
)
return extensions.flatMap((extension) => [
path.join(folder, `${MIDDLEWARE_FILENAME}.${extension}`),
path.join(folder, `${PROXY_FILENAME}.${extension}`),
])
}

export class NestedMiddlewareError extends Error {
Expand Down
12 changes: 10 additions & 2 deletions packages/next/src/build/webpack/loaders/next-middleware-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import type {
MiddlewareMatcher,
} from '../../analysis/get-page-static-info'
import { getModuleBuildInfo } from './get-module-build-info'
import { MIDDLEWARE_LOCATION_REGEXP } from '../../../lib/constants'
import {
MIDDLEWARE_LOCATION_REGEXP,
PROXY_LOCATION_REGEXP,
} from '../../../lib/constants'
import { loadEntrypoint } from '../../load-entrypoint'

export type MiddlewareLoaderOptions = {
Expand Down Expand Up @@ -49,7 +52,12 @@ export default async function middlewareLoader(this: any) {
buildInfo.nextEdgeMiddleware = {
matchers,
page:
page.replace(new RegExp(`/${MIDDLEWARE_LOCATION_REGEXP}$`), '') || '/',
page.replace(
new RegExp(
`/(${MIDDLEWARE_LOCATION_REGEXP}|${PROXY_LOCATION_REGEXP})$`
),
''
) || '/',
}
buildInfo.rootDir = rootDir
buildInfo.route = {
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export const INFINITE_CACHE = 0xfffffffe
export const MIDDLEWARE_FILENAME = 'middleware'
export const MIDDLEWARE_LOCATION_REGEXP = `(?:src/)?${MIDDLEWARE_FILENAME}`

// Patterns to detect proxy files (replacement for middleware)
export const PROXY_FILENAME = 'proxy'
export const PROXY_LOCATION_REGEXP = `(?:src/)?${PROXY_FILENAME}`

// Pattern to detect instrumentation hooks file
export const INSTRUMENTATION_HOOK_FILENAME = 'instrumentation'

Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/dev/hot-reloader-turbopack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,8 @@ export async function createHotReloaderTurbopack(
// TODO: why is this entry missing in turbopack?
if (page === '/middleware') return
if (page === '/src/middleware') return
if (page === '/proxy') return
if (page === '/src/proxy') return
if (page === '/instrumentation') return
if (page === '/src/instrumentation') return

Expand Down
13 changes: 12 additions & 1 deletion packages/next/src/server/lib/router-utils/setup-dev-bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ import {
import { getDefineEnv } from '../../../build/define-env'
import { TurbopackInternalError } from '../../../shared/lib/turbopack/internal-error'
import { normalizePath } from '../../../lib/normalize-path'
import { JSON_CONTENT_TYPE_HEADER } from '../../../lib/constants'
import {
JSON_CONTENT_TYPE_HEADER,
MIDDLEWARE_FILENAME,
PROXY_FILENAME,
} from '../../../lib/constants'
import {
createRouteTypesManifest,
writeRouteTypesManifest,
Expand Down Expand Up @@ -466,6 +470,13 @@ async function startWatcher(
continue
}
serverFields.actualMiddlewareFile = rootFile

if (rootFile.includes(MIDDLEWARE_FILENAME)) {
Log.warn(
`The "${MIDDLEWARE_FILENAME}" file convention is deprecated. Please use "${PROXY_FILENAME}" instead.`
)
}

await propagateServerField(
opts,
'actualMiddlewareFile',
Expand Down
5 changes: 4 additions & 1 deletion packages/next/src/server/web/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,10 @@ export async function adapter(
response = await propagator(request, () => {
// we only care to make async storage available for middleware
const isMiddleware =
params.page === '/middleware' || params.page === '/src/middleware'
params.page === '/middleware' ||
params.page === '/src/middleware' ||
params.page === '/proxy' ||
params.page === '/src/proxy'

if (isMiddleware) {
// if we're in an edge function, we only get a subset of `nextConfig` (no `experimental`),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-env jest */
import path from 'path'
import { nextTestSetup, FileRef } from 'e2e-utils'

describe('app dir - with proxy in src dir', () => {
const { next } = nextTestSetup({
files: {
'src/app': new FileRef(path.join(__dirname, 'app')),
'next.config.js': new FileRef(path.join(__dirname, 'next.config.js')),
'src/proxy.js': `
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'
export async function proxy(request) {
const cookie = (await cookies()).get('test-cookie')
return NextResponse.json({ cookie })
}
`,
},
})

it('works without crashing when using RequestStore', async () => {
const browser = await next.browser('/')
await browser.addCookie({
name: 'test-cookie',
value: 'test-cookie-response',
})
await browser.refresh()

const html = await browser.eval('document.documentElement.innerHTML')

expect(html).toContain('test-cookie-response')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-env jest */
import path from 'path'
import { nextTestSetup, FileRef } from 'e2e-utils'

describe('app dir - proxy without pages dir', () => {
const { next } = nextTestSetup({
files: {
app: new FileRef(path.join(__dirname, 'app')),
'next.config.js': new FileRef(path.join(__dirname, 'next.config.js')),
'proxy.js': `
import { NextResponse } from 'next/server'
export async function proxy(request) {
return new NextResponse('redirected')
}
export const config = {
matcher: '/headers'
}
`,
},
})

// eslint-disable-next-line jest/no-identical-title
it('Updates headers', async () => {
const html = await next.render('/headers')

expect(html).toContain('redirected')
})
})
Loading
Loading