Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- Fix demo projects + web frameworks with emulators (#6737)
- Fix Next.js static routes with server actions (#6664)
65 changes: 65 additions & 0 deletions src/frameworks/next/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import type {
PRERENDER_MANIFEST as PRERENDER_MANIFEST_TYPE,
ROUTES_MANIFEST as ROUTES_MANIFEST_TYPE,
APP_PATHS_MANIFEST as APP_PATHS_MANIFEST_TYPE,
SERVER_REFERENCE_MANIFEST as SERVER_REFERENCE_MANIFEST_TYPE,
} from "next/constants";
import type { WEBPACK_LAYERS as NEXTJS_WEBPACK_LAYERS } from "next/dist/lib/constants";

export const APP_PATH_ROUTES_MANIFEST: typeof APP_PATH_ROUTES_MANIFEST_TYPE =
"app-path-routes-manifest.json";
Expand All @@ -18,5 +20,68 @@ export const PAGES_MANIFEST: typeof PAGES_MANIFEST_TYPE = "pages-manifest.json";
export const PRERENDER_MANIFEST: typeof PRERENDER_MANIFEST_TYPE = "prerender-manifest.json";
export const ROUTES_MANIFEST: typeof ROUTES_MANIFEST_TYPE = "routes-manifest.json";
export const APP_PATHS_MANIFEST: typeof APP_PATHS_MANIFEST_TYPE = "app-paths-manifest.json";
export const SERVER_REFERENCE_MANIFEST: `${typeof SERVER_REFERENCE_MANIFEST_TYPE}.json` =
"server-reference-manifest.json";

export const ESBUILD_VERSION = "0.19.2";

// This is copied from Next.js source code to keep WEBPACK_LAYERS in sync with the Next.js definition.
const WEBPACK_LAYERS_NAMES = {
/**
* The layer for the shared code between the client and server bundles.
*/ shared: "shared",
/**
* React Server Components layer (rsc).
*/ reactServerComponents: "rsc",
/**
* Server Side Rendering layer for app (ssr).
*/ serverSideRendering: "ssr",
/**
* The browser client bundle layer for actions.
*/ actionBrowser: "action-browser",
/**
* The layer for the API routes.
*/ api: "api",
/**
* The layer for the middleware code.
*/ middleware: "middleware",
/**
* The layer for assets on the edge.
*/ edgeAsset: "edge-asset",
/**
* The browser client bundle layer for App directory.
*/ appPagesBrowser: "app-pages-browser",
/**
* The server bundle layer for metadata routes.
*/ appMetadataRoute: "app-metadata-route",
/**
* The layer for the server bundle for App Route handlers.
*/ appRouteHandler: "app-route-handler",
} as const;

// This is copied from Next.js source code to keep WEBPACK_LAYERS in sync with the Next.js definition.
export const WEBPACK_LAYERS: typeof NEXTJS_WEBPACK_LAYERS = {
...WEBPACK_LAYERS_NAMES,
GROUP: {
server: [
WEBPACK_LAYERS_NAMES.reactServerComponents,
WEBPACK_LAYERS_NAMES.actionBrowser,
WEBPACK_LAYERS_NAMES.appMetadataRoute,
WEBPACK_LAYERS_NAMES.appRouteHandler,
],
nonClientServerTarget: [
// plus middleware and pages api
WEBPACK_LAYERS_NAMES.middleware,
WEBPACK_LAYERS_NAMES.api,
],
app: [
WEBPACK_LAYERS_NAMES.reactServerComponents,
WEBPACK_LAYERS_NAMES.actionBrowser,
WEBPACK_LAYERS_NAMES.appMetadataRoute,
WEBPACK_LAYERS_NAMES.appRouteHandler,
WEBPACK_LAYERS_NAMES.serverSideRendering,
WEBPACK_LAYERS_NAMES.appPagesBrowser,
WEBPACK_LAYERS_NAMES.shared,
],
},
};
34 changes: 33 additions & 1 deletion src/frameworks/next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
cleanI18n,
getNextVersion,
hasStaticAppNotFoundComponent,
getRoutesWithServerAction,
} from "./utils";
import { NODE_VERSION, NPM_COMMAND_TIMEOUT_MILLIES, SHARP_VERSION, I18N_ROOT } from "../constants";
import type {
Expand All @@ -63,6 +64,7 @@ import type {
RoutesManifest,
NpmLsDepdendency,
MiddlewareManifest,
ActionManifest,
} from "./interfaces";
import {
MIDDLEWARE_MANIFEST,
Expand All @@ -72,6 +74,7 @@ import {
APP_PATH_ROUTES_MANIFEST,
APP_PATHS_MANIFEST,
ESBUILD_VERSION,
SERVER_REFERENCE_MANIFEST,
} from "./constants";
import { getAllSiteDomains, getDeploymentDomain } from "../../hosting/api";
import { logger } from "../../logger";
Expand Down Expand Up @@ -220,13 +223,16 @@ export async function build(
headers,
}));

const [appPathsManifest, appPathRoutesManifest] = await Promise.all([
const [appPathsManifest, appPathRoutesManifest, serverReferenceManifest] = await Promise.all([
readJSON<AppPathsManifest>(join(dir, distDir, "server", APP_PATHS_MANIFEST)).catch(
() => undefined,
),
readJSON<AppPathRoutesManifest>(join(dir, distDir, APP_PATH_ROUTES_MANIFEST)).catch(
() => undefined,
),
readJSON<ActionManifest>(join(dir, distDir, "server", SERVER_REFERENCE_MANIFEST)).catch(
() => undefined,
),
]);

if (appPathRoutesManifest) {
Expand Down Expand Up @@ -257,6 +263,17 @@ export async function build(
reasonsForBackend.add(`non-static component ${key}`);
}
}

if (serverReferenceManifest) {
const routesWithServerAction = getRoutesWithServerAction(
serverReferenceManifest,
appPathRoutesManifest,
);

for (const key of routesWithServerAction) {
reasonsForBackend.add(`route with server action ${key}`);
}
}
}

const isEveryRedirectSupported = nextJsRedirects
Expand Down Expand Up @@ -385,6 +402,7 @@ export async function ɵcodegenPublicDirectory(
routesManifest,
pagesManifest,
appPathRoutesManifest,
serverReferenceManifest,
] = await Promise.all([
readJSON<MiddlewareManifest>(join(sourceDir, distDir, "server", MIDDLEWARE_MANIFEST)),
readJSON<PrerenderManifest>(join(sourceDir, distDir, PRERENDER_MANIFEST)),
Expand All @@ -393,6 +411,9 @@ export async function ɵcodegenPublicDirectory(
readJSON<AppPathRoutesManifest>(join(sourceDir, distDir, APP_PATH_ROUTES_MANIFEST)).catch(
() => ({}),
),
readJSON<ActionManifest>(join(sourceDir, distDir, "server", SERVER_REFERENCE_MANIFEST)).catch(
() => ({ node: {}, edge: {}, encryptionKey: "" }),
),
]);

const appPathRoutesEntries = Object.entries(appPathRoutesManifest);
Expand Down Expand Up @@ -423,6 +444,11 @@ export async function ɵcodegenPublicDirectory(
...headersRegexesNotSupportedByHosting,
];

const staticRoutesUsingServerActions = getRoutesWithServerAction(
serverReferenceManifest,
appPathRoutesManifest,
);

const pagesManifestLikePrerender: PrerenderManifest["routes"] = Object.fromEntries(
Object.entries(pagesManifest)
.filter(([, srcRoute]) => srcRoute.endsWith(".html"))
Expand Down Expand Up @@ -457,6 +483,12 @@ export async function ɵcodegenPublicDirectory(
);
return;
}

if (staticRoutesUsingServerActions.some((it) => path === it)) {
logger.debug(`skipping ${path} due to server action`);
return;
}

const appPathRoute =
route.srcRoute && appPathRoutesEntries.find(([, it]) => it === route.srcRoute)?.[0];
const contentDist = join(sourceDir, distDir, "server", appPathRoute ? "app" : "pages");
Expand Down
26 changes: 22 additions & 4 deletions src/frameworks/next/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,29 @@ export interface AppPathsManifest {
[key: string]: string;
}

export interface AppPathRoutesManifest {
[key: string]: string;
}

export interface HostingHeadersWithSource {
source: string;
headers: HostingHeaders["headers"];
}

export type AppPathRoutesManifest = Record<string, string>;

/**
* Note: This is a copy of the type from `next/dist/build/webpack/plugins/flight-client-entry-plugin`.
* It's copied here due to type errors caused by internal dependencies of Next.js when importing that file.
*/
export type ActionManifest = {
encryptionKey: string;
node: Actions;
edge: Actions;
};
type Actions = {
[actionId: string]: {
workers: {
[name: string]: string | number;
};
layer: {
[name: string]: string;
};
};
};
32 changes: 31 additions & 1 deletion src/frameworks/next/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ import type {
MiddlewareManifestV1,
MiddlewareManifestV2,
AppPathsManifest,
AppPathRoutesManifest,
HostingHeadersWithSource,
AppPathRoutesManifest,
ActionManifest,
} from "./interfaces";
import {
APP_PATH_ROUTES_MANIFEST,
EXPORT_MARKER,
IMAGES_MANIFEST,
MIDDLEWARE_MANIFEST,
WEBPACK_LAYERS,
} from "./constants";
import { dirExistsSync, fileExistsSync } from "../../fsutils";

Expand Down Expand Up @@ -421,3 +423,31 @@ export async function hasStaticAppNotFoundComponent(
): Promise<boolean> {
return pathExists(join(sourceDir, distDir, "server", "app", "_not-found.html"));
}

/**
* Find routes using server actions by checking the server-reference-manifest.json
*/
export function getRoutesWithServerAction(
serverReferenceManifest: ActionManifest,
appPathRoutesManifest: AppPathRoutesManifest,
): string[] {
const routesWithServerAction = new Set<string>();

for (const key of Object.keys(serverReferenceManifest)) {
if (key !== "edge" && key !== "node") continue;

const edgeOrNode = serverReferenceManifest[key];

for (const actionId of Object.keys(edgeOrNode)) {
if (!edgeOrNode[actionId].layer) continue;

for (const [route, type] of Object.entries(edgeOrNode[actionId].layer)) {
if (type === WEBPACK_LAYERS.actionBrowser) {
routesWithServerAction.add(appPathRoutesManifest[route.replace("app", "")]);
}
}
}
}

return Array.from(routesWithServerAction);
}
25 changes: 25 additions & 0 deletions src/test/frameworks/next/helpers/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PrerenderManifest } from "next/dist/build";
import type { PagesManifest } from "next/dist/build/webpack/plugins/pages-manifest-plugin";
import type {
ActionManifest,
AppPathRoutesManifest,
AppPathsManifest,
} from "../../../../frameworks/next/interfaces";
Expand All @@ -15,6 +16,10 @@ export const appPathRoutesManifest: AppPathRoutesManifest = {
"/api/test/route": "/api/test",
"/api/static/route": "/api/static",
"/page": "/",
"/another-s-a/page": "/another-s-a",
"/server-action/page": "/server-action",
"/ssr/page": "/ssr",
"/server-action/edge/page": "/server-action/edge",
};

export const pagesManifest: PagesManifest = {
Expand Down Expand Up @@ -69,3 +74,23 @@ globalThis.__RSC_MANIFEST["/page"] =
export const clientReferenceManifestWithImage = `{"ssrModuleMapping":{"2306":{"*":{"id":"7833","name":"*","chunks":[],"async":false}},"2353":{"*":{"id":"8709","name":"*","chunks":[],"async":false}},"3029":{"*":{"id":"9556","name":"*","chunks":[],"async":false}},"7330":{"*":{"id":"7734","name":"*","chunks":[],"async":false}},"8531":{"*":{"id":"9150","name":"*","chunks":[],"async":false}},"9180":{"*":{"id":"2698","name":"*","chunks":[],"async":false}}},"edgeSSRModuleMapping":{},"clientModules":{"/app-path/node_modules/next/dist/client/components/app-router.js":{"id":2353,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/app-router.js":{"id":2353,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/layout-router.js":{"id":9180,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/layout-router.js":{"id":9180,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/render-from-template-context.js":{"id":2306,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/render-from-template-context.js":{"id":2306,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/static-generation-searchparams-bailout-provider.js":{"id":8531,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/static-generation-searchparams-bailout-provider.js":{"id":8531,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/error-boundary.js":{"id":7330,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/error-boundary.js":{"id":7330,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/image-component.js":{"id":3029,"name":"*","chunks":["931:static/chunks/app/page-8d47763b987bba19.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/image-component.js":{"id":3029,"name":"*","chunks":["931:static/chunks/app/page-8d47763b987bba19.js"],"async":false},"/app-path/node_modules/next/font/google/target.css?{\"path\":\"src/app/layout.tsx\",\"import\":\"Inter\",\"arguments\":[{\"subsets\":[\"latin\"]}],\"variableName\":\"inter\"}":{"id":670,"name":"*","chunks":["185:static/chunks/app/layout-09ef1f5c8b0e56d1.js"],"async":false},"/app-path/src/app/globals.css":{"id":8410,"name":"*","chunks":["185:static/chunks/app/layout-09ef1f5c8b0e56d1.js"],"async":false}},"entryCSSFiles":{"/app-path/src/app/page":[],"/app-path/src/app/layout":["static/css/110a35ea7c81b899.css"]}}`;

export const clientReferenceManifestWithoutImage = `{"ssrModuleMapping":{"2306":{"*":{"id":"7833","name":"*","chunks":[],"async":false}},"2353":{"*":{"id":"8709","name":"*","chunks":[],"async":false}},"3029":{"*":{"id":"9556","name":"*","chunks":[],"async":false}},"7330":{"*":{"id":"7734","name":"*","chunks":[],"async":false}},"8531":{"*":{"id":"9150","name":"*","chunks":[],"async":false}},"9180":{"*":{"id":"2698","name":"*","chunks":[],"async":false}}},"edgeSSRModuleMapping":{},"clientModules":{"/app-path/node_modules/next/dist/client/components/app-router.js":{"id":2353,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/app-router.js":{"id":2353,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/layout-router.js":{"id":9180,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/layout-router.js":{"id":9180,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/render-from-template-context.js":{"id":2306,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/render-from-template-context.js":{"id":2306,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/static-generation-searchparams-bailout-provider.js":{"id":8531,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/static-generation-searchparams-bailout-provider.js":{"id":8531,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/client/components/error-boundary.js":{"id":7330,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/dist/esm/client/components/error-boundary.js":{"id":7330,"name":"*","chunks":["272:static/chunks/webpack-76fd8b39fe914c29.js","253:static/chunks/bce60fc1-8c4748991edb1ec4.js","698:static/chunks/698-1321e6d13d35448d.js"],"async":false},"/app-path/node_modules/next/font/google/target.css?{\"path\":\"src/app/layout.tsx\",\"import\":\"Inter\",\"arguments\":[{\"subsets\":[\"latin\"]}],\"variableName\":\"inter\"}":{"id":670,"name":"*","chunks":["185:static/chunks/app/layout-09ef1f5c8b0e56d1.js"],"async":false},"/app-path/src/app/globals.css":{"id":8410,"name":"*","chunks":["185:static/chunks/app/layout-09ef1f5c8b0e56d1.js"],"async":false}},"entryCSSFiles":{"/app-path/src/app/page":[],"/app-path/src/app/layout":["static/css/110a35ea7c81b899.css"]}}`;

export const serverReferenceManifest: ActionManifest = {
node: {
"123": {
workers: { "app/another-s-a/page": 123, "app/server-action/page": 123 },
layer: {
"app/another-s-a/page": "action-browser",
"app/server-action/page": "action-browser",
"app/ssr/page": "rsc",
},
},
},
edge: {
"123": {
workers: { "app/server-action/edge/page": 123 },
layer: { "app/server-action/edge/page": "action-browser" },
},
},
encryptionKey: "456",
};
10 changes: 10 additions & 0 deletions src/test/frameworks/next/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
getHeadersFromMetaFiles,
isUsingNextImageInAppDirectory,
getNextVersion,
getRoutesWithServerAction,
} from "../../../frameworks/next/utils";

import * as frameworksUtils from "../../../frameworks/utils";
Expand Down Expand Up @@ -63,6 +64,7 @@ import {
pageClientReferenceManifestWithoutImage,
clientReferenceManifestWithImage,
clientReferenceManifestWithoutImage,
serverReferenceManifest,
} from "./helpers";
import { pathsWithCustomRoutesInternalPrefix } from "./helpers/i18n";

Expand Down Expand Up @@ -523,4 +525,12 @@ describe("Next.js utils", () => {
expect(getNextVersion("")).to.be.undefined;
});
});

describe("getRoutesWithServerAction", () => {
it("should get routes with server action", () => {
expect(
getRoutesWithServerAction(serverReferenceManifest, appPathRoutesManifest),
).to.deep.equal(["/another-s-a", "/server-action", "/server-action/edge"]);
});
});
});