Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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/weak-houses-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

feat: r2 adapter for the incremental cache
1 change: 1 addition & 0 deletions examples/common/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const apps = [
// overrides
"d1-tag-next",
"memory-queue",
"r2-incremental-cache",
// bugs
"gh-119",
"gh-219",
Expand Down
3 changes: 1 addition & 2 deletions examples/e2e/app-router/open-next.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import d1TagCache from "@opennextjs/cloudflare/d1-tag-cache";
import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache";
import doQueue from "@opennextjs/cloudflare/durable-queue";
import shardedTagCache from "@opennextjs/cloudflare/do-sharded-tag-cache";
import doQueue from "@opennextjs/cloudflare/durable-queue";

export default defineCloudflareConfig({
incrementalCache: kvIncrementalCache,
Expand Down
47 changes: 47 additions & 0 deletions examples/overrides/r2-incremental-cache/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
Binary file not shown.
14 changes: 14 additions & 0 deletions examples/overrides/r2-incremental-cache/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
html,
body {
max-width: 100vw;
overflow-x: hidden;
height: 100vh;
display: flex;
flex-direction: column;
}

footer {
padding: 1rem;
display: flex;
justify-content: end;
}
25 changes: 25 additions & 0 deletions examples/overrides/r2-incremental-cache/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Metadata } from "next";
import "./globals.css";

import { getCloudflareContext } from "@opennextjs/cloudflare";

export const metadata: Metadata = {
title: "SSG App",
description: "An app in which all the routes are SSG'd",
};

export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cloudflareContext = await getCloudflareContext({
async: true,
});

return (
<html lang="en">
<body>{children}</body>
</html>
);
}
17 changes: 17 additions & 0 deletions examples/overrides/r2-incremental-cache/app/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.page {
display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
flex: 1;
border: 3px solid gray;
margin: 1rem;
margin-block-end: 0;
}

.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
16 changes: 16 additions & 0 deletions examples/overrides/r2-incremental-cache/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styles from "./page.module.css";

export const revalidate = 5;

export default async function Home() {
// We purposefully wait for 2 seconds to allow deduplication to occur
await new Promise((resolve) => setTimeout(resolve, 2000));
return (
<div className={styles.page}>
<main className={styles.main}>
<h1>Hello from a Statically generated page</h1>
<p data-testid="date-local">{Date.now()}</p>
</main>
</div>
);
}
36 changes: 36 additions & 0 deletions examples/overrides/r2-incremental-cache/e2e/base.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from "@playwright/test";

test.describe("r2-incremental-cache", () => {
test("the index page should work", async ({ page }) => {
await page.goto("/");
await expect(page.getByText("Hello from a Statically generated page")).toBeVisible();
});

test("the index page should revalidate", async ({ page, request }) => {
// We need to make sure the page is loaded and is a HIT
// If it is STALE, the next hit may have an updated date and thus fail the test
let cacheHeaders = "";
do {
const req = await request.get("/");
cacheHeaders = req.headers()["x-nextjs-cache"];
await page.waitForTimeout(500);
} while (cacheHeaders !== "HIT");

await page.goto("/");
const firstDate = await page.getByTestId("date-local").textContent();

await page.reload();
let newDate = await page.getByTestId("date-local").textContent();
expect(newDate).toBe(firstDate);

await page.waitForTimeout(5000);

do {
await page.reload();
newDate = await page.getByTestId("date-local").textContent();
await page.waitForTimeout(1000);
} while (newDate === firstDate);

expect(newDate).not.toBe(firstDate);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { configurePlaywright } from "../../../common/config-e2e";

// Here we don't want to run the tests in parallel
export default configurePlaywright("r2-incremental-cache", {
isCI: !!process.env.CI,
parallel: false,
multipleBrowsers: false,
});
11 changes: 11 additions & 0 deletions examples/overrides/r2-incremental-cache/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { NextConfig } from "next";
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";

initOpenNextCloudflareForDev();

const nextConfig: NextConfig = {
typescript: { ignoreBuildErrors: true },
eslint: { ignoreDuringBuilds: true },
};

export default nextConfig;
14 changes: 14 additions & 0 deletions examples/overrides/r2-incremental-cache/open-next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import d1TagCache from "@opennextjs/cloudflare/d1-tag-cache";
import memoryQueue from "@opennextjs/cloudflare/memory-queue";
import r2IncrementalCache from "@opennextjs/cloudflare/r2-incremental-cache";
import { withRegionalCache } from "@opennextjs/cloudflare/regional-cache";

export default defineCloudflareConfig({
incrementalCache: withRegionalCache(r2IncrementalCache, {
mode: "long-lived",
shouldLazilyUpdateOnCacheHit: true,
}),
tagCache: d1TagCache,
queue: memoryQueue,
});
29 changes: 29 additions & 0 deletions examples/overrides/r2-incremental-cache/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "r2-incremental-cache",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS revalidations\"",
"build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare --populateCache=local",
"preview": "pnpm build:worker && pnpm wrangler dev",
"e2e": "playwright test -c e2e/playwright.config.ts"
},
"dependencies": {
"react": "catalog:e2e",
"react-dom": "catalog:e2e",
"next": "catalog:e2e"
},
"devDependencies": {
"@opennextjs/cloudflare": "workspace:*",
"@playwright/test": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:e2e",
"@types/react-dom": "catalog:e2e",
"typescript": "catalog:",
"wrangler": "catalog:"
}
}
27 changes: 27 additions & 0 deletions examples/overrides/r2-incremental-cache/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
31 changes: 31 additions & 0 deletions examples/overrides/r2-incremental-cache/wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": ".open-next/worker.js",
"name": "r2-incremental-cache",
"compatibility_date": "2025-02-04",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"d1_databases": [
{
"binding": "NEXT_CACHE_D1",
"database_id": "NEXT_CACHE_D1",
"database_name": "NEXT_CACHE_D1"
}
],
"services": [
{
"binding": "NEXT_CACHE_REVALIDATION_WORKER",
"service": "r2-incremental-cache"
}
],
"r2_buckets": [
{
"binding": "NEXT_CACHE_R2_BUCKET",
"bucket_name": "NEXT_CACHE_R2_BUCKET",
"preview_bucket_name": "NEXT_CACHE_R2_BUCKET"
}
]
}
4 changes: 4 additions & 0 deletions packages/cloudflare/src/api/cloudflare-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ declare global {
NEXT_CACHE_D1_REVALIDATIONS_TABLE?: string;
// Service binding for the worker itself to be able to call itself from within the worker
NEXT_CACHE_REVALIDATION_WORKER?: Service;
// R2 bucket used for the incremental cache
NEXT_CACHE_R2_BUCKET?: R2Bucket;
// Prefix used for the R2 incremental cache bucket
NEXT_CACHE_R2_PREFIX?: string;
// Durable Object namespace to use for the durable object queue handler
NEXT_CACHE_REVALIDATION_DURABLE_OBJECT?: DurableObjectNamespace<DurableObjectQueueHandler>;
// Durables object namespace to use for the sharded tag cache
Expand Down
6 changes: 6 additions & 0 deletions packages/cloudflare/src/api/internal/incremental-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { CacheValue } from "@opennextjs/aws/types/overrides.js";

export type IncrementalCacheEntry<IsFetch extends boolean> = {
value: CacheValue<IsFetch>;
lastModified: number;
};
Loading