Skip to content

Commit 8c9ff6b

Browse files
author
Luca Forstner
authored
fix(nextjs): Fix middleware detection logic (#9637)
1 parent 08818d8 commit 8c9ff6b

File tree

3 files changed

+59
-15
lines changed

3 files changed

+59
-15
lines changed

packages/nextjs/src/config/loaders/wrappingLoader.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ const routeHandlerWrapperTemplatePath = path.resolve(__dirname, '..', 'templates
4141
const routeHandlerWrapperTemplateCode = fs.readFileSync(routeHandlerWrapperTemplatePath, { encoding: 'utf8' });
4242

4343
export type WrappingLoaderOptions = {
44-
pagesDir: string;
45-
appDir: string;
44+
pagesDir: string | undefined;
45+
appDir: string | undefined;
4646
pageExtensionRegex: string;
4747
excludeServerRoutes: Array<RegExp | string>;
4848
wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component' | 'sentry-init' | 'route-handler';
@@ -101,6 +101,11 @@ export default function wrappingLoader(
101101
return;
102102
}
103103
} else if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') {
104+
if (pagesDir === undefined) {
105+
this.callback(null, userCode, userModuleSourceMap);
106+
return;
107+
}
108+
104109
// Get the parameterized route name from this page's filepath
105110
const parameterizedPagesRoute = path
106111
// Get the path of the file insde of the pages directory
@@ -137,6 +142,11 @@ export default function wrappingLoader(
137142
// Inject the route and the path to the file we're wrapping into the template
138143
templateCode = templateCode.replace(/__ROUTE__/g, parameterizedPagesRoute.replace(/\\/g, '\\\\'));
139144
} else if (wrappingTargetKind === 'server-component' || wrappingTargetKind === 'route-handler') {
145+
if (appDir === undefined) {
146+
this.callback(null, userCode, userModuleSourceMap);
147+
return;
148+
}
149+
140150
// Get the parameterized route name from this page's filepath
141151
const parameterizedPagesRoute = path
142152
// Get the path of the file insde of the app directory

packages/nextjs/src/config/webpack.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,26 +98,31 @@ export function constructWebpackConfigFunction(
9898
],
9999
});
100100

101-
let pagesDirPath: string;
101+
let pagesDirPath: string | undefined;
102102
const maybePagesDirPath = path.join(projectDir, 'pages');
103+
const maybeSrcPagesDirPath = path.join(projectDir, 'src', 'pages');
103104
if (fs.existsSync(maybePagesDirPath) && fs.lstatSync(maybePagesDirPath).isDirectory()) {
104-
pagesDirPath = path.join(projectDir, 'pages');
105-
} else {
106-
pagesDirPath = path.join(projectDir, 'src', 'pages');
105+
pagesDirPath = maybePagesDirPath;
106+
} else if (fs.existsSync(maybeSrcPagesDirPath) && fs.lstatSync(maybeSrcPagesDirPath).isDirectory()) {
107+
pagesDirPath = maybeSrcPagesDirPath;
107108
}
108109

109-
let appDirPath: string;
110+
let appDirPath: string | undefined;
110111
const maybeAppDirPath = path.join(projectDir, 'app');
112+
const maybeSrcAppDirPath = path.join(projectDir, 'src', 'app');
111113
if (fs.existsSync(maybeAppDirPath) && fs.lstatSync(maybeAppDirPath).isDirectory()) {
112-
appDirPath = path.join(projectDir, 'app');
113-
} else {
114-
appDirPath = path.join(projectDir, 'src', 'app');
114+
appDirPath = maybeAppDirPath;
115+
} else if (fs.existsSync(maybeSrcAppDirPath) && fs.lstatSync(maybeSrcAppDirPath).isDirectory()) {
116+
appDirPath = maybeSrcAppDirPath;
115117
}
116118

117-
const apiRoutesPath = path.join(pagesDirPath, 'api');
119+
const apiRoutesPath = pagesDirPath ? path.join(pagesDirPath, 'api') : undefined;
118120

119-
const middlewareJsPath = path.join(pagesDirPath, '..', 'middleware.js');
120-
const middlewareTsPath = path.join(pagesDirPath, '..', 'middleware.ts');
121+
const middlewareLocationFolder = pagesDirPath
122+
? path.join(pagesDirPath, '..')
123+
: appDirPath
124+
? path.join(appDirPath, '..')
125+
: projectDir;
121126

122127
// Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
123128
const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js'];
@@ -151,6 +156,7 @@ export function constructWebpackConfigFunction(
151156
const isPageResource = (resourcePath: string): boolean => {
152157
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
153158
return (
159+
pagesDirPath !== undefined &&
154160
normalizedAbsoluteResourcePath.startsWith(pagesDirPath + path.sep) &&
155161
!normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) &&
156162
dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext))
@@ -167,7 +173,10 @@ export function constructWebpackConfigFunction(
167173

168174
const isMiddlewareResource = (resourcePath: string): boolean => {
169175
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
170-
return normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath;
176+
return (
177+
normalizedAbsoluteResourcePath.startsWith(middlewareLocationFolder + path.sep) &&
178+
!!normalizedAbsoluteResourcePath.match(/[\\/]middleware\.(js|jsx|ts|tsx)$/)
179+
);
171180
};
172181

173182
const isServerComponentResource = (resourcePath: string): boolean => {
@@ -176,6 +185,7 @@ export function constructWebpackConfigFunction(
176185
// ".js, .jsx, or .tsx file extensions can be used for Pages"
177186
// https://beta.nextjs.org/docs/routing/pages-and-layouts#pages:~:text=.js%2C%20.jsx%2C%20or%20.tsx%20file%20extensions%20can%20be%20used%20for%20Pages.
178187
return (
188+
appDirPath !== undefined &&
179189
normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) &&
180190
!!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/)
181191
);
@@ -184,6 +194,7 @@ export function constructWebpackConfigFunction(
184194
const isRouteHandlerResource = (resourcePath: string): boolean => {
185195
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
186196
return (
197+
appDirPath !== undefined &&
187198
normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) &&
188199
!!normalizedAbsoluteResourcePath.match(/[\\/]route\.(js|jsx|ts|tsx)$/)
189200
);

packages/nextjs/test/config/loaders.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// mock helper functions not tested directly in this file
22
import './mocks';
33

4+
import * as fs from 'fs';
5+
46
import type { ModuleRuleUseProperty, WebpackModuleRule } from '../../src/config/types';
57
import {
68
clientBuildContext,
@@ -11,6 +13,9 @@ import {
1113
} from './fixtures';
1214
import { materializeFinalWebpackConfig } from './testUtils';
1315

16+
const existsSyncSpy = jest.spyOn(fs, 'existsSync');
17+
const lstatSyncSpy = jest.spyOn(fs, 'lstatSync');
18+
1419
type MatcherResult = { pass: boolean; message: () => string };
1520

1621
expect.extend({
@@ -85,6 +90,7 @@ describe('webpack loaders', () => {
8590
});
8691
});
8792

93+
// For these tests we assume that we have an app and pages folder in {rootdir}/src
8894
it.each([
8995
{
9096
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/pages/testPage.tsx',
@@ -139,8 +145,9 @@ describe('webpack loaders', () => {
139145
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/middleware.ts',
140146
expectedWrappingTargetKind: 'middleware',
141147
},
148+
// Since we assume we have a pages file in src middleware will only be included in the build if it is also in src
142149
{
143-
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/middleware.tsx',
150+
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/middleware.tsx',
144151
expectedWrappingTargetKind: undefined,
145152
},
146153
{
@@ -182,6 +189,22 @@ describe('webpack loaders', () => {
182189
])(
183190
'should apply the right wrappingTargetKind with wrapping loader ($resourcePath)',
184191
async ({ resourcePath, expectedWrappingTargetKind }) => {
192+
// We assume that we have an app and pages folder in {rootdir}/src
193+
existsSyncSpy.mockImplementation(path => {
194+
if (
195+
path.toString().startsWith('/Users/Maisey/projects/squirrelChasingSimulator/app') ||
196+
path.toString().startsWith('/Users/Maisey/projects/squirrelChasingSimulator/pages')
197+
) {
198+
return false;
199+
}
200+
return true;
201+
});
202+
203+
// @ts-expect-error Too lazy to mock the entire thing
204+
lstatSyncSpy.mockImplementation(() => ({
205+
isDirectory: () => true,
206+
}));
207+
185208
const finalWebpackConfig = await materializeFinalWebpackConfig({
186209
exportedNextConfig,
187210
incomingWebpackConfig: serverWebpackConfig,

0 commit comments

Comments
 (0)