Skip to content
Open
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
43 changes: 43 additions & 0 deletions website/pages/Page.cacheable.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { assert, assertEquals } from "@std/assert";
import { PAGE_CACHE_ALLOWED_KEY } from "@deco/deco/blocks";
import { loader } from "./Page.tsx";

// Minimal fake AppContext exercising exactly what the loader touches.
const fakeCtx = () =>
({
bag: new Map<symbol, unknown>(),
global: [],
theme: undefined,
errorPage: undefined,
resolverId: "root",
// deno-lint-ignore no-explicit-any
get: () => Promise.resolve({}) as any,
avoidRedirectingToEditor: undefined,
defaultImageQuality: undefined,
// deno-lint-ignore no-explicit-any
}) as any;

const baseProps = { name: "home", sections: [] };
const req = () => new Request("https://farm.example/");

Deno.test("cacheable=true opts the page into caching", async () => {
const ctx = fakeCtx();
await loader({ ...baseProps, cacheable: true }, req(), ctx);
assert(
ctx.bag.has(PAGE_CACHE_ALLOWED_KEY),
"expected PAGE_CACHE_ALLOWED_KEY to be set",
);
assertEquals(ctx.bag.get(PAGE_CACHE_ALLOWED_KEY), true);
});

Deno.test("cacheable=false does NOT opt in (default behavior preserved)", async () => {
const ctx = fakeCtx();
await loader({ ...baseProps, cacheable: false }, req(), ctx);
assert(!ctx.bag.has(PAGE_CACHE_ALLOWED_KEY));
});

Deno.test("cacheable omitted does NOT opt in", async () => {
const ctx = fakeCtx();
await loader({ ...baseProps }, req(), ctx);
assert(!ctx.bag.has(PAGE_CACHE_ALLOWED_KEY));
});
18 changes: 17 additions & 1 deletion website/pages/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
type ComponentFunc,
type ComponentMetadata,
type Page,
PAGE_CACHE_ALLOWED_KEY,
type Section,
} from "@deco/deco/blocks";
import { logger } from "@deco/deco/o11y";
Expand Down Expand Up @@ -51,6 +52,15 @@ export interface Props {
/** @hide true */
seo?: Section<SEOSection>;
sections: Sections;
/**
* @title Allow CDN caching
* @description Opt this page into CDN page caching. Only enable for public,
* non-personalized pages: the response is still forced to `no-store` if a
* loader sets a cookie, vetoes caching (cache "no-store" / null cache key),
* or a non-cacheable matcher/flag is active. Leave off for anything that
* renders per-user content without one of those signals.
*/
cacheable?: boolean;
/** @hide true */
unindexedDomain?: boolean;
}
Expand Down Expand Up @@ -155,10 +165,16 @@ function Page(
}

export const loader = async (
{ sections, ...restProps }: Props,
{ sections, cacheable, ...restProps }: Props,
req: Request,
ctx: AppContext,
) => {
// Opt the page into CDN caching. Same mechanism the VTEX middleware uses;
// the runtime still applies the no-store guards (set-cookie, vary, flags).
if (cacheable) {
ctx.bag?.set(PAGE_CACHE_ALLOWED_KEY, true);
}

const url = new URL(req.url);
const devMode = url.searchParams.has("__d");
const unindexedDomain = noIndexedDomains.some((domain) =>
Expand Down
Loading