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
5 changes: 5 additions & 0 deletions .changeset/thirty-bears-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": minor
---

Add support for Next 16
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Install's node, pnpm, restores cache, and then installs dependencie

inputs:
node-version:
default: 18.x
default: 20.x
registry-url:
default: "https://registry.npmjs.org"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm # cache pnpm store
node-version: 18.18.2
node-version: 20.x

- name: Install packages
run: pnpm install
Expand Down
5 changes: 0 additions & 5 deletions examples/app-pages-router/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
poweredByHeader: false,
cleanDistDir: true,
transpilePackages: ["@example/shared"],
output: "standalone",
// outputFileTracingRoot: "../sst",
eslint: {
ignoreDuringBuilds: true,
},
trailingSlash: true,
skipTrailingSlashRedirect: true,
};
Expand Down
5 changes: 4 additions & 1 deletion examples/app-pages-router/open-next.config.local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export default {
},
loader: "fs-dev",
},
dangerous: {
enableCacheInterception: true,
},
// You can override the build command here so that you don't have to rebuild next every time you make a change
//buildCommand: "echo 'No build command'",
// buildCommand: "echo 'No build command'",
} satisfies OpenNextConfig;
2 changes: 1 addition & 1 deletion examples/app-pages-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"openbuild": "node ../../packages/open-next/dist/index.js build --build-command \"npx turbo build\"",
"openbuild:local": "node ../../packages/open-next/dist/index.js build --config-path open-next.config.local.ts",
"openbuild:local:start": "PORT=3003 tsx proxy.ts",
"openbuild:local:start": "PORT=3003 tsx on-proxy.ts",
"dev": "next dev --turbopack --port 3003",
"build": "next build",
"start": "next start --port 3003",
Expand Down
12 changes: 9 additions & 3 deletions examples/app-pages-router/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
Expand All @@ -23,11 +23,17 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules",
"open-next.config.ts",
"open-next.config.local.ts",
"proxy.ts"
"on-proxy.ts"
]
}
5 changes: 4 additions & 1 deletion examples/app-router/app/api/after/revalidate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ export function POST() {
() =>
new Promise<void>((resolve) =>
setTimeout(() => {
revalidateTag("date");
revalidateTag("date", {
// We want to expire the "date" tag immediately
expire: 0,
});
resolve();
}, 5000),
),
Expand Down
10 changes: 7 additions & 3 deletions examples/app-router/app/api/after/ssg/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import { NextResponse } from "next/server";
export const dynamic = "force-static";

export async function GET() {
const dateFn = unstable_cache(() => new Date().toISOString(), ["date"], {
tags: ["date"],
});
const dateFn = unstable_cache(
async () => new Date().toISOString(),
["date"],
{
tags: ["date"],
},
);
const date = await dateFn();
return NextResponse.json({ date });
}
2 changes: 1 addition & 1 deletion examples/app-router/app/api/revalidate-tag/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { revalidateTag } from "next/cache";
export const dynamic = "force-dynamic";

export async function GET() {
revalidateTag("revalidate");
revalidateTag("revalidate", { expire: 0 });

return new Response("ok");
}
2 changes: 1 addition & 1 deletion examples/app-router/app/isr-data-cache/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ async function getTime() {
return new Date().toISOString();
}

const cachedTime = unstable_cache(getTime, { revalidate: false });
const cachedTime = unstable_cache(getTime, ["getTime"], { revalidate: false });

export const revalidate = 10;

Expand Down
3 changes: 3 additions & 0 deletions examples/app-router/app/og/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export default async function Image() {
// For convenience, we can re-use the exported opengraph-image
// size config to also set the ImageResponse's width and height.
...size,
headers: {
"cache-control": "public, immutable, no-transform, max-age=31536000",
},
},
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

export function middleware(request: NextRequest) {
export default function proxy(request: NextRequest) {
const path = request.nextUrl.pathname; //new URL(request.url).pathname;

const host = request.headers.get("host");
Expand Down
10 changes: 8 additions & 2 deletions examples/app-router/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
Expand All @@ -23,7 +23,13 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules",
"open-next.config.ts",
Expand Down
4 changes: 0 additions & 4 deletions examples/pages-router/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ const nextConfig: NextConfig = {
cleanDistDir: true,
reactStrictMode: true,
output: "standalone",
// outputFileTracingRoot: "../sst",
eslint: {
ignoreDuringBuilds: true,
},
headers: async () => [
{
source: "/",
Expand Down
2 changes: 2 additions & 0 deletions examples/pages-router/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export function middleware(request: NextRequest) {
return NextResponse.next({
headers: {
"x-from-middleware": "true",
// We need to disable caching in cloudfront to ensure we always hit the origin for this test
"cache-control": "private, no-store",
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion examples/pages-router/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"baseUrl": ".",
"paths": {
Expand Down
6 changes: 3 additions & 3 deletions examples/sst/stacks/OpenNextReferenceImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export class OpenNextCdkReferenceImplementation extends Construct {
code: Code.fromAsset(
path.join(this.openNextBasePath, ".open-next/dynamodb-provider"),
),
runtime: Runtime.NODEJS_18_X,
runtime: Runtime.NODEJS_20_X,
timeout: Duration.minutes(15),
memorySize: 128,
environment: {
Expand Down Expand Up @@ -293,7 +293,7 @@ export class OpenNextCdkReferenceImplementation extends Construct {
"",
),
),
runtime: Runtime.NODEJS_18_X,
runtime: Runtime.NODEJS_20_X,
timeout: Duration.seconds(30),
});
consumer.addEventSource(
Expand Down Expand Up @@ -330,7 +330,7 @@ export class OpenNextCdkReferenceImplementation extends Construct {
const environment = this.getEnvironment();
const fn = new CdkFunction(this, `${key}Function`, {
architecture: Architecture.ARM_64,
runtime: Runtime.NODEJS_18_X,
runtime: Runtime.NODEJS_20_X,
handler: origin.handler,
code: Code.fromAsset(path.join(this.openNextBasePath, origin.bundle)),
environment,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"openbuild:local": "turbo run openbuild:local",
"openbuild:local:start": "turbo run openbuild:local:start",
"clean": "turbo run clean && rm -rf node_modules pnpm-lock.yaml",
"lint": "biome check",
"lint:fix": "biome check --fix",
Expand Down
9 changes: 9 additions & 0 deletions packages/open-next/src/adapters/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ export default class Cache {
}
if (cacheData?.type === "page" || cacheData?.type === "app") {
if (globalThis.isNextAfter15 && cacheData?.type === "app") {
const segmentData = new Map<string, Buffer>();
if (cacheData.segmentData) {
for (const [segmentPath, segmentContent] of Object.entries(
cacheData.segmentData ?? {},
)) {
segmentData.set(segmentPath, Buffer.from(segmentContent));
}
}
return {
lastModified: _lastModified,
value: {
Expand All @@ -157,6 +165,7 @@ export default class Cache {
status: meta?.status,
headers: meta?.headers,
postponed: meta?.postponed,
segmentData,
},
} as CacheHandlerValue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export async function optimizeImage(
const { isAbsolute, href } = imageParams;

const imageUpstream = isAbsolute
? await fetchExternalImage(href)
? //@ts-expect-error - fetchExternalImage signature has changed in Next.js 16, it has an extra boolean parameter.
// https://github.com/vercel/next.js/blob/bfe2ab4/packages/next/src/server/image-optimizer.ts#L711
await fetchExternalImage(href)
: await fetchInternalImage(
href,
// @ts-expect-error - It is supposed to be an IncomingMessage object, but only the headers are used.
Expand Down
13 changes: 11 additions & 2 deletions packages/open-next/src/build/copyTracedFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ File ${serverPath} does not exist

// Only files that are actually copied
const tracedFiles: string[] = [];
const erroredFiles: string[] = [];
//Actually copy the files
filesToCopy.forEach((to, from) => {
// We don't want to copy excluded packages (e.g. sharp)
Expand All @@ -285,7 +286,15 @@ File ${serverPath} does not exist
}
}
} else {
copyFileSync(from, to);
// Adding this inside a try-catch to handle errors on Next 16+
// where some files listed in the .nft.json might not be present in the standalone folder
// TODO: investigate that further - is it expected?
try {
copyFileSync(from, to);
} catch (e) {
logger.debug("Error copying file:", e);
erroredFiles.push(to);
}
}
});

Expand Down Expand Up @@ -383,7 +392,7 @@ File ${serverPath} does not exist
logger.debug("copyTracedFiles:", Date.now() - tsStart, "ms");

return {
tracedFiles,
tracedFiles: tracedFiles.filter((f) => !erroredFiles.includes(f)),
nodePackages,
manifests,
};
Expand Down
22 changes: 22 additions & 0 deletions packages/open-next/src/build/createAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export function createCacheAssets(options: buildHelper.BuildOptions) {
const isFileSkipped = (relativePath: string) =>
relativePath.endsWith(".js") ||
relativePath.endsWith(".js.nft.json") ||
// We skip manifest files as well
relativePath.endsWith("-manifest.json") ||
// We skip the segment rsc files as they are treated in a different way
relativePath.endsWith(".segment.rsc") ||
(relativePath.endsWith(".html") && htmlPages.has(relativePath));

// Merge cache files into a single file
Expand Down Expand Up @@ -169,6 +173,23 @@ export function createCacheAssets(options: buildHelper.BuildOptions) {
logger.warn(`Skipping invalid cache file: ${cacheFilePath}`);
return;
}

// If we have a meta file, and it contains segmentPaths, we need to add them to the cache file
const segments: Record<string, string> = Array.isArray(
cacheFileMeta?.segmentPaths,
)
? Object.fromEntries(
cacheFileMeta!.segmentPaths.map((segmentPath: string) => {
const absoluteSegmentPath = path.join(
files.meta!.replace(/\.meta$/, ".segments"),
`${segmentPath}.segment.rsc`,
);
const segmentContent = fs.readFileSync(absoluteSegmentPath, "utf8");
return [segmentPath, segmentContent];
}),
)
: {};

const cacheFileContent = {
type: files.body ? "route" : files.json ? "page" : "app",
meta: cacheFileMeta,
Expand All @@ -184,6 +205,7 @@ export function createCacheAssets(options: buildHelper.BuildOptions) {
: "utf8",
)
: undefined,
segmentData: Object.keys(segments).length > 0 ? segments : undefined,
};

// Ensure directory exists before writing
Expand Down
Loading