[next] fix app dir edge functions with basePath (#10465)

x-ref: https://github.com/vercel/vercel/pull/10394
This commit is contained in:
JJ Kasper
2023-09-07 15:03:12 -07:00
committed by GitHub
parent 335fd70a68
commit caaba0d685
13 changed files with 159 additions and 24 deletions

View File

@@ -0,0 +1,5 @@
---
"@vercel/next": patch
---
Fix Next.js with `basePath` + Edge runtime + App Router on a top level `page.jsx`

View File

@@ -91,6 +91,7 @@ import {
getOperationType, getOperationType,
isApiPage, isApiPage,
getFunctionsConfigManifest, getFunctionsConfigManifest,
normalizeEdgeFunctionPath,
} from './utils'; } from './utils';
export const version = 2; export const version = 2;
@@ -2720,7 +2721,15 @@ async function getServerlessPages(params: {
for (const edgeFunctionFile of Object.keys( for (const edgeFunctionFile of Object.keys(
middlewareManifest?.functions ?? {} middlewareManifest?.functions ?? {}
)) { )) {
const edgePath = (edgeFunctionFile.slice(1) || 'index') + '.js'; let edgePath =
middlewareManifest?.functions?.[edgeFunctionFile].name ||
edgeFunctionFile;
edgePath = normalizeEdgeFunctionPath(
edgePath,
params.appPathRoutesManifest || {}
);
edgePath = (edgePath || 'index') + '.js';
delete normalizedAppPaths[edgePath]; delete normalizedAppPaths[edgePath];
delete pages[edgePath]; delete pages[edgePath];
} }

View File

@@ -1245,10 +1245,9 @@ export async function serverBuild({
if (ogRoute.endsWith('/route')) { if (ogRoute.endsWith('/route')) {
continue; continue;
} }
route = path.posix.join( route = normalizeIndexOutput(
'./', path.posix.join('./', entryDirectory, route === '/' ? '/index' : route),
entryDirectory, true
route === '/' ? '/index' : route
); );
if (lambdas[route]) { if (lambdas[route]) {

View File

@@ -2344,7 +2344,7 @@ export function normalizeIndexOutput(
outputName: string, outputName: string,
isServerMode: boolean isServerMode: boolean
) { ) {
if (outputName !== '/index' && isServerMode) { if (outputName !== 'index' && outputName !== '/index' && isServerMode) {
return outputName.replace(/\/index$/, ''); return outputName.replace(/\/index$/, '');
} }
return outputName; return outputName;
@@ -2521,6 +2521,29 @@ function normalizeRegions(regions: Regions): undefined | string | string[] {
return newRegions; return newRegions;
} }
export function normalizeEdgeFunctionPath(
shortPath: string,
appPathRoutesManifest: Record<string, string>
) {
if (
shortPath.startsWith('app/') &&
(shortPath.endsWith('/page') ||
shortPath.endsWith('/route') ||
shortPath === 'app/_not-found')
) {
const ogRoute = shortPath.replace(/^app\//, '/');
shortPath = (
appPathRoutesManifest[ogRoute] ||
shortPath.replace(/(^|\/)(page|route)$/, '')
).replace(/^\//, '');
if (!shortPath || shortPath === '/') {
shortPath = 'index';
}
}
return shortPath;
}
export async function getMiddlewareBundle({ export async function getMiddlewareBundle({
entryPath, entryPath,
outputDirectory, outputDirectory,
@@ -2699,27 +2722,19 @@ export async function getMiddlewareBundle({
// app/index/page -> index/index // app/index/page -> index/index
if (shortPath.startsWith('pages/')) { if (shortPath.startsWith('pages/')) {
shortPath = shortPath.replace(/^pages\//, ''); shortPath = shortPath.replace(/^pages\//, '');
} else if ( } else {
shortPath.startsWith('app/') && shortPath = normalizeEdgeFunctionPath(shortPath, appPathRoutesManifest);
(shortPath.endsWith('/page') ||
shortPath.endsWith('/route') ||
shortPath === 'app/_not-found')
) {
const ogRoute = shortPath.replace(/^app\//, '/');
shortPath = (
appPathRoutesManifest[ogRoute] ||
shortPath.replace(/(^|\/)(page|route)$/, '')
).replace(/^\//, '');
if (!shortPath || shortPath === '/') {
shortPath = 'index';
}
} }
if (routesManifest?.basePath) { if (routesManifest?.basePath) {
shortPath = path.posix shortPath = normalizeIndexOutput(
.join(routesManifest.basePath, shortPath) path.posix.join(
.replace(/^\//, ''); './',
routesManifest?.basePath,
shortPath.replace(/^\//, '')
),
true
);
} }
worker.edgeFunction.name = shortPath; worker.edgeFunction.name = shortPath;

View File

@@ -0,0 +1,7 @@
export const runtime = 'edge';
const Home = () => {
return <div>another</div>;
};
export default Home;

View File

@@ -0,0 +1,7 @@
export const runtime = 'edge';
const Home = () => {
return <div>dynamic</div>;
};
export default Home;

View File

@@ -0,0 +1,11 @@
export const runtime = 'edge';
const Layout = ({ children }) => {
return (
<html>
<body>{children}</body>
</html>
);
};
export default Layout;

View File

@@ -0,0 +1,7 @@
export const runtime = 'edge';
const Home = () => {
return <div>Home</div>;
};
export default Home;

View File

@@ -0,0 +1,7 @@
export const runtime = 'edge';
const Home = () => {
return <div>test</div>;
};
export default Home;

View File

@@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
basePath: '/test',
};
module.exports = nextConfig;

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "latest",
"react-dom": "latest"
},
"ignoreNextjsUpdates": true
}

View File

@@ -0,0 +1,4 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/next" }]
}

View File

@@ -553,3 +553,52 @@ it('Should de-dupe correctly when limit is close (uncompressed)', async () => {
expect(lambdas.size).toBe(2); expect(lambdas.size).toBe(2);
expect(lambdas.size).toBeLessThan(totalLambdas); expect(lambdas.size).toBeLessThan(totalLambdas);
}); });
it('should handle edge functions in app with basePath', async () => {
const {
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'edge-app-dir-basepath'));
console.error(output);
expect(output['test']).toBeDefined();
expect(output['test']).toBeDefined();
expect(output['test'].type).toBe('EdgeFunction');
expect(output['test'].type).toBe('EdgeFunction');
expect(output['test/another']).toBeDefined();
expect(output['test/another.rsc']).toBeDefined();
expect(output['test/another'].type).toBe('EdgeFunction');
expect(output['test/another.rsc'].type).toBe('EdgeFunction');
expect(output['test/dynamic/[slug]']).toBeDefined();
expect(output['test/dynamic/[slug].rsc']).toBeDefined();
expect(output['test/dynamic/[slug]'].type).toBe('EdgeFunction');
expect(output['test/dynamic/[slug].rsc'].type).toBe('EdgeFunction');
expect(output['test/dynamic/[slug]']).toBeDefined();
expect(output['test/dynamic/[slug].rsc']).toBeDefined();
expect(output['test/dynamic/[slug]'].type).toBe('EdgeFunction');
expect(output['test/dynamic/[slug].rsc'].type).toBe('EdgeFunction');
expect(output['test/test']).toBeDefined();
expect(output['test/test.rsc']).toBeDefined();
expect(output['test/test'].type).toBe('EdgeFunction');
expect(output['test/test.rsc'].type).toBe('EdgeFunction');
expect(output['test/_not-found']).toBeDefined();
expect(output['test/_not-found'].type).toBe('Lambda');
const lambdas = new Set();
const edgeFunctions = new Set();
for (const item of Object.values(output)) {
if (item.type === 'Lambda') {
lambdas.add(item);
} else if (item.type === 'EdgeFunction') {
edgeFunctions.add(item);
}
}
expect(lambdas.size).toBe(1);
expect(edgeFunctions.size).toBe(4);
});