Skip to content

Commit 787c1b2

Browse files
authored
Support Next.js 404 pages (#78)
* Fix "Cannot find package 'next'" * Support Next.js 404 pages
1 parent 6395849 commit 787c1b2

File tree

3 files changed

+54
-6
lines changed

3 files changed

+54
-6
lines changed

.changeset/wet-dryers-battle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": minor
3+
---
4+
5+
Support Next.js 404 pages

packages/open-next/src/adapters/server-adapter.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import NextServer from "next/dist/server/next-server.js";
1414
import { loadConfig } from "./util.js";
1515
import { isBinaryContentType } from "./binary.js";
1616
import { debug } from "./logger.js";
17+
import type { PublicAssets } from "../build.js";
1718

1819
setNextjsServerWorkingDirectory();
1920
const nextDir = path.join(__dirname, ".next");
21+
const openNextDir = path.join(__dirname, ".open-next");
2022
const config = loadConfig(nextDir);
2123
const htmlPages = loadHtmlPages();
24+
const publicAssets = loadPublicAssets();
2225
debug({ nextDir });
2326

2427
// Create a NextServer
@@ -132,6 +135,18 @@ export async function handler(
132135
const parser = isCloudFrontEvent
133136
? eventParser.cloudfront(event as CloudFrontRequestEvent)
134137
: eventParser.apiv2(event as APIGatewayProxyEventV2);
138+
139+
// WORKAROUND: public/ static files served by the server function (AWS specific) — https://github.com/serverless-stack/open-next#workaround-public-static-files-served-by-the-server-function-aws-specific
140+
if (
141+
publicAssets[parser.rawPath] === "file" ||
142+
publicAssets[parser.rawPath.split("/")[0]] === "dir"
143+
) {
144+
return isCloudFrontEvent
145+
? formatCloudFrontFailoverResponse(event as CloudFrontRequestEvent)
146+
: formatApiv2FailoverResponse();
147+
}
148+
149+
// Process Next.js request
135150
const reqProps = {
136151
method: parser.method,
137152
url: parser.url,
@@ -142,8 +157,6 @@ export async function handler(
142157
debug("IncomingMessage constructor props", reqProps);
143158
const req = new IncomingMessage(reqProps);
144159
const res = new ServerResponse({ method: reqProps.method });
145-
146-
// Process Next.js request
147160
await processRequest(req, res);
148161

149162
// Format Next.js response to Lambda response
@@ -165,10 +178,7 @@ export async function handler(
165178
}
166179

167180
return isCloudFrontEvent
168-
? // WORKAROUND: public/ static files served by the server function (AWS specific) — https://github.com/serverless-stack/open-next#workaround-public-static-files-served-by-the-server-function-aws-specific
169-
statusCode === 404
170-
? formatCloudFrontFailoverResponse(event as CloudFrontRequestEvent)
171-
: formatCloudFrontResponse({ statusCode, headers, isBase64Encoded, body })
181+
? formatCloudFrontResponse({ statusCode, headers, isBase64Encoded, body })
172182
: formatApiv2Response({ statusCode, headers, isBase64Encoded, body });
173183
}
174184

@@ -189,6 +199,12 @@ function loadHtmlPages() {
189199
.map(([key]) => key);
190200
}
191201

202+
function loadPublicAssets() {
203+
const filePath = path.join(openNextDir, "public-files.json");
204+
const json = fs.readFileSync(filePath, "utf-8");
205+
return JSON.parse(json) as PublicAssets;
206+
}
207+
192208
async function processRequest(req: IncomingMessage, res: ServerResponse) {
193209
// @ts-ignore
194210
// Next.js doesn't parse body if the property exists
@@ -246,6 +262,10 @@ function formatApiv2Response({
246262
return response;
247263
}
248264

265+
function formatApiv2FailoverResponse() {
266+
return { statusCode: 503 };
267+
}
268+
249269
function formatCloudFrontResponse({
250270
statusCode,
251271
headers: rawHeaders,

packages/open-next/src/build.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const appPath = process.cwd();
99
const outputDir = ".open-next";
1010
const tempDir = path.join(outputDir, ".build");
1111

12+
export type PublicAssets = Record<string, "file" | "dir">;
13+
1214
export async function build() {
1315
// Pre-build validation
1416
printVersion();
@@ -110,6 +112,19 @@ function initOutputDir() {
110112
fs.mkdirSync(tempDir, { recursive: true });
111113
}
112114

115+
function readTopLevelPublicFilesAndDirs() {
116+
const publicPath = path.join(appPath, "public");
117+
118+
const items: PublicAssets = {};
119+
120+
fs.readdirSync(publicPath).map((file) => {
121+
items[`/${file}`] = fs.statSync(path.join(publicPath, file)).isDirectory()
122+
? "dir"
123+
: "file";
124+
});
125+
return items;
126+
}
127+
113128
function createServerBundle(monorepoRoot: string) {
114129
console.info(`Bundling server function...`);
115130

@@ -165,6 +180,14 @@ function createServerBundle(monorepoRoot: string) {
165180
[`export * from "./${packagePath}/index.mjs";`].join("")
166181
);
167182
}
183+
184+
// Save a list of top level files in /public
185+
const outputOpenNextPath = path.join(outputPath, packagePath, ".open-next");
186+
fs.mkdirSync(outputOpenNextPath, { recursive: true });
187+
fs.writeFileSync(
188+
path.join(outputOpenNextPath, "public-files.json"),
189+
JSON.stringify(readTopLevelPublicFilesAndDirs())
190+
);
168191
}
169192

170193
function createImageOptimizationBundle() {

0 commit comments

Comments
 (0)