Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
87defa6
feat(cloudflare): automate response store deployment
james-elicx Sep 14, 2026
d6dd4ec
fix(cloudflare): keep generated caching opt-in
james-elicx Sep 14, 2026
6ec47b0
fix(cloudflare): bind web previews to response store
james-elicx Sep 14, 2026
987698a
fix(cloudflare): deploy web response store through CLI
james-elicx Sep 14, 2026
3416466
feat(cloudflare): configure response store deployment mode
james-elicx Sep 14, 2026
5da5321
fix(create): parse response store mode option
james-elicx Sep 14, 2026
94b4683
fix(init): update existing response store mode
james-elicx Sep 14, 2026
02ca5c5
fix(ci): keep preview response store available
james-elicx Sep 14, 2026
129479c
fix(ci): let main deploy the demo response store
james-elicx Sep 14, 2026
b1f33a8
refactor(cloudflare): rename response store deploy option
james-elicx Sep 14, 2026
d409d24
fix(init): derive data cache CDN selection
james-elicx Sep 14, 2026
7e547a5
fix(cloudflare): reject response store config collisions
james-elicx Sep 14, 2026
0394a5f
fix(init): preserve response store service options
james-elicx Sep 14, 2026
9183dd7
fix(cloudflare): harden response store configuration
james-elicx Sep 14, 2026
2ab45b6
fix(init): keep no-cache scaffolds deployable
james-elicx Sep 14, 2026
28619f5
fix(cloudflare): scope response store provisioning
james-elicx Sep 14, 2026
a3aef5d
test(response-store): allow skipped revision ids
james-elicx Sep 14, 2026
6624eb3
test(init): align prerender rerun cache choice
james-elicx Sep 14, 2026
7666376
fix(cloudflare): validate generated resource discovery
james-elicx Sep 14, 2026
9007bce
chore(cloudflare): clarify deployment phase output
james-elicx Sep 14, 2026
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
27 changes: 8 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,6 @@ jobs:
runs-on: ubuntu-latest
env:
CNA_CLOUDFLARE_WORKER: create-next-app-cloudflare
# Public test namespace already used by examples/response-store-demo.
CNA_CLOUDFLARE_KV_NAMESPACE_ID: b38f84f642aa4bd4ba17a37bb516f0b5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down Expand Up @@ -337,7 +335,7 @@ jobs:
working-directory: ${{ runner.temp }}/cna-cloudflare
run: >-
vp exec vinext init --skip-check --platform=cloudflare
--data-cache=kv
--cdn-cache=response-store
--image-optimization=cloudflare-images

- name: Configure generated Cloudflare deployment
Expand All @@ -349,11 +347,6 @@ jobs:
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
config.name = process.env.CNA_CLOUDFLARE_WORKER;
config.preview_urls = true;
for (const namespace of config.kv_namespaces ?? []) {
if (namespace.binding === "VINEXT_KV_CACHE") {
namespace.id = process.env.CNA_CLOUDFLARE_KV_NAMESPACE_ID;
}
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
'

Expand All @@ -363,15 +356,15 @@ jobs:

- name: Verify generated Cloudflare build output
working-directory: ${{ runner.temp }}/cna-cloudflare
run: test -f dist/server/wrangler.json
run: |
test -f dist/server/wrangler.json
test -f dist/server/vinext-response-store/wrangler.json

create-vinext-app-cloudflare:
name: create-vinext-app (Cloudflare build)
runs-on: ubuntu-latest
env:
CVA_CLOUDFLARE_WORKER: create-vinext-app-cloudflare
# Public test namespace already used by examples/response-store-demo.
CVA_CLOUDFLARE_KV_NAMESPACE_ID: b38f84f642aa4bd4ba17a37bb516f0b5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down Expand Up @@ -403,8 +396,7 @@ jobs:
--skip-install
--disable-git
--platform=cloudflare
--cdn-cache=data-cache
--data-cache=kv
--cdn-cache=response-store
--image-optimization=cloudflare-images

- name: Pin pnpm in scaffolded project to vinext's version
Expand Down Expand Up @@ -445,11 +437,6 @@ jobs:
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
config.name = process.env.CVA_CLOUDFLARE_WORKER;
config.preview_urls = true;
for (const namespace of config.kv_namespaces ?? []) {
if (namespace.binding === "VINEXT_KV_CACHE") {
namespace.id = process.env.CVA_CLOUDFLARE_KV_NAMESPACE_ID;
}
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
'

Expand All @@ -459,7 +446,9 @@ jobs:

- name: Verify generated Cloudflare build output
working-directory: ${{ runner.temp }}/cva-cloudflare
run: test -f dist/server/wrangler.json
run: |
test -f dist/server/wrangler.json
test -f dist/server/vinext-response-store/wrangler.json

e2e:
name: E2E (${{ matrix.label }})
Expand Down
14 changes: 4 additions & 10 deletions .github/workflows/create-vinext-app-cloudflare-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ jobs:
runs-on: ubuntu-latest
env:
CVA_CLOUDFLARE_WORKER: create-vinext-app-cloudflare
# Public test namespace already used by examples/response-store-demo.
CVA_CLOUDFLARE_KV_NAMESPACE_ID: b38f84f642aa4bd4ba17a37bb516f0b5
CVA_CLOUDFLARE_EXPECTED_TEXT: vinext + Cloudflare Workers
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -56,8 +54,7 @@ jobs:
--skip-install
--disable-git
--platform=cloudflare
--cdn-cache=data-cache
--data-cache=kv
--cdn-cache=response-store
--image-optimization=cloudflare-images

- name: Pin pnpm in scaffolded project to vinext's version
Expand Down Expand Up @@ -98,11 +95,6 @@ jobs:
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
config.name = process.env.CVA_CLOUDFLARE_WORKER;
config.preview_urls = true;
for (const namespace of config.kv_namespaces ?? []) {
if (namespace.binding === "VINEXT_KV_CACHE") {
namespace.id = process.env.CVA_CLOUDFLARE_KV_NAMESPACE_ID;
}
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
'

Expand All @@ -112,7 +104,9 @@ jobs:

- name: Verify generated Cloudflare build output
working-directory: ${{ runner.temp }}/cva-cloudflare
run: test -f dist/server/wrangler.json
run: |
test -f dist/server/wrangler.json
test -f dist/server/vinext-response-store/wrangler.json

- name: Deploy generated Cloudflare project
working-directory: ${{ runner.temp }}/cva-cloudflare
Expand Down
49 changes: 7 additions & 42 deletions .github/workflows/deploy-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
run: |
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
prefix="pr-${{ github.event.pull_request.number }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
service_name="${prefix}-rs-service"
service_name="pr-${{ github.event.pull_request.number }}-response-store-demo-response-store"
else
prefix="main"
service_name="vinext-workers-response-store-service-poc"
Expand Down Expand Up @@ -203,30 +203,19 @@ jobs:
if: matrix.example.static_export != true
run: vp build
working-directory: ${{ matrix.example.working_directory }}

- name: Bind Response Store demo to this run's cache Worker
if: matrix.example.name == 'response-store-demo'
working-directory: ${{ matrix.example.working_directory }}
env:
RESPONSE_STORE_SERVICE: ${{ needs.response_store.outputs.service_name }}
run: |
# Node reads the value from process.env.
# shellcheck disable=SC2016
node -e '
const fs = require("node:fs");
const path = "dist/server/wrangler.json";
const config = JSON.parse(fs.readFileSync(path, "utf8"));
const binding = config.services?.find(({ binding }) => binding === "RESPONSE_STORE");
if (!binding) throw new Error("Missing RESPONSE_STORE service binding");
binding.service = process.env.RESPONSE_STORE_SERVICE;
fs.writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
'
VINEXT_RESPONSE_STORE_SERVICE_NAME: ${{ matrix.example.name == 'response-store-demo' && github.event_name == 'pull_request' && needs.response_store.outputs.service_name || '' }}
Comment thread
james-elicx marked this conversation as resolved.

- name: Build static export
if: matrix.example.static_export == true
run: vp exec vinext build
working-directory: ${{ matrix.example.working_directory }}

- name: Verify generated Response Store Worker
if: matrix.example.name == 'response-store-demo' || matrix.example.name == 'web'
working-directory: ${{ matrix.example.working_directory }}
run: test -f dist/server/vinext-response-store/wrangler.json

- name: Verify static export artifact
if: matrix.example.static_export == true
run: node scripts/verify-output.mjs
Expand Down Expand Up @@ -347,30 +336,6 @@ jobs:
tests/e2e/cloudflare-workers/web-worker.spec.ts
tests/e2e/cloudflare-workers/optimistic-search-navigation.spec.ts

cleanup-response-store:
name: Clean up Workers Response Store service
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
needs: [response_store, deploy, smoke-test, deployed-e2e]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup

- name: Delete cache Worker
working-directory: packages/workers-response-store
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
SERVICE_NAME: ${{ needs.response_store.outputs.service_name }}
run: |
if [[ -n "$SERVICE_NAME" ]]; then
vp exec wrangler delete "$SERVICE_NAME" --force
fi

comment:
name: Comment Preview URLs
runs-on: ubuntu-latest
Expand Down
8 changes: 2 additions & 6 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import vinext from "vinext";
import { defineConfig } from "vite-plus";
import { cdnAdapter } from "@vinext/cloudflare/cache/cdn-adapter";
import { kvDataAdapter } from "@vinext/cloudflare/cache/kv-data-adapter";
import { responseStoreAdapter } from "@vinext/cloudflare/cache/response-store-adapter";
import { imagesOptimizer } from "@vinext/cloudflare/images/images-optimizer";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
plugins: [
vinext({
cache: {
cdn: cdnAdapter(),
data: kvDataAdapter(),
},
cache: responseStoreAdapter(),
images: {
optimizer: imagesOptimizer(),
},
Expand Down
16 changes: 0 additions & 16 deletions apps/web/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,6 @@
"migrations_dir": "./migrations",
},
],
// KV namespace used as the backing store for vinext's ISR CacheHandler.
// Pages opt into ISR with `export const revalidate = <seconds>`, and the
// rendered output (RSC + HTML) is cached here keyed by route + searchParams.
// Created via: wrangler kv namespace create vinext-web-cache
"kv_namespaces": [
{
"binding": "VINEXT_KV_CACHE",
"id": "08075d24ec854a19a52c13f031723def",
},
],
"r2_buckets": [
{
"binding": "PERFORMANCE_PROFILES",
Expand All @@ -55,10 +45,4 @@
"secrets": {
"required": ["COMPAT_INGEST_SECRET"],
},
"cache": {
"enabled": true,
},
"version_metadata": {
"binding": "CF_VERSION_METADATA",
},
}
6 changes: 5 additions & 1 deletion examples/response-store-demo/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { responseStoreAdapter } from "@vinext/cloudflare/cache/response-store-ad
import { cloudflare } from "@cloudflare/vite-plugin";

const selfContained = process.env.VINEXT_RESPONSE_STORE_MODE === "self-contained";
const serviceName = process.env.VINEXT_RESPONSE_STORE_SERVICE_NAME;
const outputRoot = selfContained ? ".vinext/response-store-self-contained" : "dist";

export default defineConfig({
plugins: [
vinext({
cache: responseStoreAdapter({ mode: selfContained ? "self-contained" : "service-binding" }),
cache: responseStoreAdapter({
mode: selfContained ? "self-contained" : "service-binding",
...(serviceName ? { serviceName } : {}),
}),
clientOutDir: `${outputRoot}/client`,
rscOutDir: `${outputRoot}/server`,
ssrOutDir: `${outputRoot}/server/ssr`,
Expand Down
8 changes: 0 additions & 8 deletions examples/response-store-demo/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,4 @@
"enabled": true,
},
},
"services": [
{
"binding": "RESPONSE_STORE",
"service": "vinext-workers-response-store-service-poc",
"entrypoint": "ResponseStoreService",
},
],
"version_metadata": { "binding": "CF_VERSION_METADATA" },
}
49 changes: 41 additions & 8 deletions packages/cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,54 @@ representation variants cannot collide.

`responseStoreAdapter()` replaces both `cdnAdapter()` and `kvDataAdapter()`.
It defaults to a separate cache Worker reached through the `RESPONSE_STORE`
service binding. To deploy storage and cache entrypoints with the application
instead, select self-contained mode:
service binding. The Cloudflare build emits that Worker and its binding, and
`vinext-cloudflare deploy` deploys it before the application Worker. Wrangler
provisions the R2 bucket and SQLite Durable Object automatically, so the app
does not need storage IDs or a separate deployment command.

Resource names can be fixed explicitly when they need to match existing
infrastructure:

```ts
vinext({
cache: responseStoreAdapter({
serviceName: "my-response-store",
r2BucketName: "my-response-store-bodies",
}),
});
```

The service name also identifies the service-owned Durable Object namespace.
Keeping it stable reuses its metadata. To bind a compatible Response Store
Worker managed outside the vinext deployment, set `shouldDeployService: false`:

```ts
vinext({
cache: responseStoreAdapter({
serviceName: "shared-response-store",
shouldDeployService: false,
}),
});
```

When omitted, names are derived from the application Worker. Setting
`shouldDeployService` to `false` emits only the application service binding; the
external Worker and its R2 and Durable Object resources remain independently
managed.

To deploy storage and cache entrypoints with the application instead, select
self-contained mode:

```ts
import { responseStoreAdapter } from "@vinext/cloudflare/cache/response-store-adapter";

vinext({ cache: responseStoreAdapter({ mode: "self-contained" }) });
```

Self-contained Workers must bind `CACHE_BODIES` to R2, bind the SQLite
`CacheMetadata` Durable Object as `CACHE_METADATA`, export
`ResponseStoreBinding` with Workers Cache enabled, and include the
`CF_VERSION_METADATA` version-metadata binding. The default Worker entrypoint
must keep Workers Cache disabled. This removes the cache Worker and service
binding without changing cache behavior or the application API.
The build adds the required R2, SQLite Durable Object, Workers Cache entrypoint,
and version-metadata bindings to the application Worker. This removes the cache
Worker and service binding without changing cache behavior or the application
API.

## Deploy

Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"build": "vp pack",
"prepack": "vp run build",
"dev": "vp pack --watch",
"test": "vp run vinext#build && vp run @cloudflare/workers-response-store#build && vp run @vinext/cloudflare#build && VINEXT_RESPONSE_STORE_MODE=self-contained pnpm --filter response-store-demo run build && pnpm --filter response-store-demo run build && pnpm --filter @cloudflare/workers-response-store run build:service-workers && vitest run"
"test": "vp run vinext#build && vp run @cloudflare/workers-response-store#build && vp run @vinext/cloudflare#build && VINEXT_RESPONSE_STORE_MODE=self-contained pnpm --filter response-store-demo run build && pnpm --filter response-store-demo run build && vitest run"
},
"dependencies": {
"@cloudflare/workers-response-store": "workspace:*"
Expand Down
Loading
Loading