Skip to content

feat: prefix for kv cache keys #616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 30, 2025
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/two-paws-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

feat: prefix for kv cache keys
10 changes: 7 additions & 3 deletions packages/cloudflare/src/api/cloudflare-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type { Context, RunningCodeOptions } from "node:vm";

import type { GetPlatformProxyOptions } from "wrangler";

import type { DOQueueHandler } from "./durable-objects/queue";
import { DOShardedTagCache } from "./durable-objects/sharded-tag-cache";
import type { DOQueueHandler } from "./durable-objects/queue.js";
import type { DOShardedTagCache } from "./durable-objects/sharded-tag-cache.js";
import type { PREFIX_ENV_NAME as KV_CACHE_PREFIX_ENV_NAME } from "./overrides/incremental-cache/kv-incremental-cache.js";
import type { PREFIX_ENV_NAME as R2_CACHE_PREFIX_ENV_NAME } from "./overrides/incremental-cache/r2-incremental-cache.js";

declare global {
interface CloudflareEnv {
Expand All @@ -19,11 +21,13 @@ declare global {

// KV used for the incremental cache
NEXT_INC_CACHE_KV?: KVNamespace;
// Prefix used for the KV incremental cache key
[KV_CACHE_PREFIX_ENV_NAME]?: string;

// R2 bucket used for the incremental cache
NEXT_INC_CACHE_R2_BUCKET?: R2Bucket;
// Prefix used for the R2 incremental cache bucket
NEXT_INC_CACHE_R2_PREFIX?: string;
[R2_CACHE_PREFIX_ENV_NAME]?: string;

// D1 db used for the tag cache
NEXT_TAG_CACHE_D1?: D1Database;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
import { createHash } from "node:crypto";

import { error } from "@opennextjs/aws/adapters/logger.js";
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
import { IgnorableError } from "@opennextjs/aws/utils/error.js";

import { getCloudflareContext } from "../../cloudflare-context.js";
import { debugCache, FALLBACK_BUILD_ID, IncrementalCacheEntry } from "../internal.js";
import { computeCacheKey, debugCache, IncrementalCacheEntry } from "../internal.js";

export const NAME = "cf-kv-incremental-cache";

export const BINDING_NAME = "NEXT_INC_CACHE_KV";

export type KeyOptions = {
isFetch?: boolean;
buildId?: string;
};

export function computeCacheKey(key: string, options: KeyOptions) {
const { isFetch = false, buildId = FALLBACK_BUILD_ID } = options;
const hash = createHash("sha256").update(key).digest("hex");
return `${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
}
export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_KV_PREFIX";

/**
* Open Next cache based on Cloudflare KV.
*
* The prefix that the cache entries are stored under can be configured with the `NEXT_INC_CACHE_KV_PREFIX`
* environment variable, and defaults to `incremental-cache`.
*
* Note: The class is instantiated outside of the request context.
* The cloudflare context and process.env are not initialized yet
* when the constructor is called.
Expand Down Expand Up @@ -106,6 +98,7 @@ class KVIncrementalCache implements IncrementalCache {

protected getKVKey(key: string, isFetch?: boolean): string {
return computeCacheKey(key, {
prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
buildId: process.env.NEXT_BUILD_ID,
isFetch,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
import { createHash } from "node:crypto";

import { error } from "@opennextjs/aws/adapters/logger.js";
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js";
import { IgnorableError } from "@opennextjs/aws/utils/error.js";

import { getCloudflareContext } from "../../cloudflare-context.js";
import { debugCache, FALLBACK_BUILD_ID } from "../internal.js";
import { computeCacheKey, debugCache } from "../internal.js";

export const NAME = "cf-r2-incremental-cache";

export const BINDING_NAME = "NEXT_INC_CACHE_R2_BUCKET";

export const PREFIX_ENV_NAME = "NEXT_INC_CACHE_R2_PREFIX";
export const DEFAULT_PREFIX = "incremental-cache";

export type KeyOptions = {
isFetch?: boolean;
directory?: string;
buildId?: string;
};

export function computeCacheKey(key: string, options: KeyOptions) {
const { isFetch = false, directory = DEFAULT_PREFIX, buildId = FALLBACK_BUILD_ID } = options;
const hash = createHash("sha256").update(key).digest("hex");
return `${directory}/${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
}

/**
* An instance of the Incremental Cache that uses an R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`) as it's
Expand Down Expand Up @@ -91,7 +76,7 @@ class R2IncrementalCache implements IncrementalCache {

protected getR2Key(key: string, isFetch?: boolean): string {
return computeCacheKey(key, {
directory: getCloudflareContext().env[PREFIX_ENV_NAME],
prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
buildId: process.env.NEXT_BUILD_ID,
isFetch,
});
Expand Down
16 changes: 16 additions & 0 deletions packages/cloudflare/src/api/overrides/internal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { createHash } from "node:crypto";

import { CacheValue } from "@opennextjs/aws/types/overrides.js";

export type IncrementalCacheEntry<IsFetch extends boolean> = {
Expand All @@ -12,3 +14,17 @@ export const debugCache = (name: string, ...args: unknown[]) => {
};

export const FALLBACK_BUILD_ID = "no-build-id";

export const DEFAULT_PREFIX = "incremental-cache";

export type KeyOptions = {
isFetch: boolean | undefined;
prefix: string | undefined;
buildId: string | undefined;
};

export function computeCacheKey(key: string, options: KeyOptions) {
const { isFetch = false, prefix = DEFAULT_PREFIX, buildId = FALLBACK_BUILD_ID } = options;
const hash = createHash("sha256").update(key).digest("hex");
return `${prefix}/${buildId}/${hash}.${isFetch ? "fetch" : "cache"}`.replace(/\/+/g, "/");
}
11 changes: 6 additions & 5 deletions packages/cloudflare/src/cli/commands/populate-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ import { unstable_readConfig } from "wrangler";

import {
BINDING_NAME as KV_CACHE_BINDING_NAME,
computeCacheKey as computeKVCacheKey,
NAME as KV_CACHE_NAME,
PREFIX_ENV_NAME as KV_CACHE_PREFIX_ENV_NAME,
} from "../../api/overrides/incremental-cache/kv-incremental-cache.js";
import {
BINDING_NAME as R2_CACHE_BINDING_NAME,
computeCacheKey as computeR2CacheKey,
NAME as R2_CACHE_NAME,
PREFIX_ENV_NAME as R2_CACHE_PREFIX_ENV_NAME,
} from "../../api/overrides/incremental-cache/r2-incremental-cache.js";
import {
CACHE_DIR as STATIC_ASSETS_CACHE_DIR,
NAME as STATIC_ASSETS_CACHE_NAME,
} from "../../api/overrides/incremental-cache/static-assets-incremental-cache.js";
import { computeCacheKey } from "../../api/overrides/internal.js";
import {
BINDING_NAME as D1_TAG_BINDING_NAME,
NAME as D1_TAG_NAME,
Expand Down Expand Up @@ -113,8 +113,8 @@ function populateR2IncrementalCache(
const assets = getCacheAssets(options);

for (const { fullPath, key, buildId, isFetch } of tqdm(assets)) {
const cacheKey = computeR2CacheKey(key, {
directory: process.env[R2_CACHE_PREFIX_ENV_NAME],
const cacheKey = computeCacheKey(key, {
prefix: process.env[R2_CACHE_PREFIX_ENV_NAME],
buildId,
isFetch,
});
Expand Down Expand Up @@ -146,7 +146,8 @@ function populateKVIncrementalCache(
const assets = getCacheAssets(options);

for (const { fullPath, key, buildId, isFetch } of tqdm(assets)) {
const cacheKey = computeKVCacheKey(key, {
const cacheKey = computeCacheKey(key, {
prefix: process.env[KV_CACHE_PREFIX_ENV_NAME],
buildId,
isFetch,
});
Expand Down