@decocms/nextjs 8.0.0 → 8.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -359,9 +359,7 @@ implementation file) can opt into these, each read as a literal
359
359
 
360
360
  ## Verifying your setup
361
361
 
362
- `examples/nextjs-smoke` in this monorepo is a minimal, real Next.js App
363
- Router build exercising all four surfaces end to end — `withDeco` rewrites,
364
- the catch-all route, the RSC preview page, `createNextSetup`, and a resolved
365
- page render. Its preview fixture is a real interactive Client Component, so
366
- the build also guards the server/client boundary described above. Use it as
367
- a working reference if any of the above doesn't compose the way you expect.
362
+ No example app in this monorepo exercises these surfaces any more:
363
+ `examples/nextjs-smoke` now follows the next-major Next.js guide, which uses
364
+ `@decocms/blocks` alone. The surfaces above are covered by this package's unit
365
+ tests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/nextjs",
3
- "version": "8.0.0",
3
+ "version": "8.1.0-next.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -29,8 +29,8 @@
29
29
  "lint:unused": "knip"
30
30
  },
31
31
  "dependencies": {
32
- "@decocms/blocks": "8.0.0",
33
- "@decocms/blocks-admin": "8.0.0"
32
+ "@decocms/blocks": "8.1.0-next.0",
33
+ "@decocms/blocks-admin": "8.1.0-next.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "next": ">=15.0.0",
@@ -1,252 +1,16 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * Preview-mode indicator.
5
- *
6
- * The draft-preview flow (see `draft.ts`) carries the pointer in a cookie
7
- * across in-preview navigation, so after the first click the URL no longer
8
- * shows `?__draft=` — a reviewer can forget they are looking at unpublished
9
- * content and mistake it for what is live. This badge is the always-on signal:
10
- * whenever a draft is bound it floats over the page, and clicking it offers the
11
- * two things a reviewer actually needs — leave preview, or hand the exact draft
12
- * version to someone else.
13
- *
14
- * Deliberately self-contained: `"use client"`, inline styles (no dependency on
15
- * the host site's CSS/Tailwind), and a very high z-index, so it renders the
16
- * same on any consumer site. Only mounted when a draft is active
17
- * (`DraftPreviewIndicator` gates it), so it never costs ordinary traffic.
4
+ * The preview-mode badge now lives in the runtime core so both bindings share
5
+ * one implementation — see `@decocms/blocks/preview`. This module is a thin
6
+ * re-export kept for back-compat: sites and `DraftPreviewIndicator` import the
7
+ * badge (and the pure `buildShareUrl`/`buildExitUrl` helpers) from here.
8
+ * `isFramed` stays internal to `@decocms/blocks/preview` — it was never part of
9
+ * this package's public surface.
18
10
  */
19
- import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
20
- import { DECO_MARK_DATA_URI } from "./decoMark";
21
- import { DRAFT_PARAM } from "./draftConstants";
22
-
23
- export interface DraftPreviewBadgeProps {
24
- /** The `<host>@<version>` token this render is bound to. */
25
- pointer: string;
26
- }
27
-
28
- /**
29
- * URL that re-enters this exact draft version — what "Share Preview Link"
30
- * copies. Pins the version (the token carries the content ETag), so the
31
- * recipient sees the same working-tree state, not merely "some draft".
32
- *
33
- * Pure and exported for tests: builds off a full href so it needs no DOM.
34
- */
35
- export function buildShareUrl(href: string, pointer: string): string {
36
- const url = new URL(href);
37
- url.searchParams.set(DRAFT_PARAM, pointer);
38
- return url.toString();
39
- }
40
-
41
- /**
42
- * URL that leaves preview mode — `?__draft=off`, the exit sentinel the
43
- * middleware/page pair understands (it clears the cookie and renders
44
- * published). Pure and exported for tests.
45
- */
46
- export function buildExitUrl(href: string): string {
47
- const url = new URL(href);
48
- url.searchParams.set(DRAFT_PARAM, "off");
49
- return url.toString();
50
- }
51
-
52
- const DECO_GREEN = "#0b3d1e";
53
- /** Toast background — the deco lime. Text sits on it in dark green for contrast. */
54
- const DECO_LIME = "#d0ec1a";
55
-
56
- /** Leaving preview: an arrow back to the published site. */
57
- function ExitIcon() {
58
- return (
59
- <svg
60
- width={14}
61
- height={14}
62
- viewBox="0 0 24 24"
63
- fill="none"
64
- stroke="currentColor"
65
- strokeWidth={2}
66
- strokeLinecap="round"
67
- strokeLinejoin="round"
68
- aria-hidden="true"
69
- >
70
- <path d="M19 12H5" />
71
- <path d="M12 19l-7-7 7-7" />
72
- </svg>
73
- );
74
- }
75
-
76
- /** Sharing the draft: the classic three-node share glyph. */
77
- function ShareIcon() {
78
- return (
79
- <svg
80
- width={14}
81
- height={14}
82
- viewBox="0 0 24 24"
83
- fill="none"
84
- stroke="currentColor"
85
- strokeWidth={2}
86
- strokeLinecap="round"
87
- strokeLinejoin="round"
88
- aria-hidden="true"
89
- >
90
- <circle cx={18} cy={5} r={3} />
91
- <circle cx={6} cy={12} r={3} />
92
- <circle cx={18} cy={19} r={3} />
93
- <path d="M8.59 13.51l6.83 3.98" />
94
- <path d="M15.41 6.51l-6.82 3.98" />
95
- </svg>
96
- );
97
- }
98
-
99
- export function DraftPreviewBadge({ pointer }: DraftPreviewBadgeProps) {
100
- const [open, setOpen] = useState(false);
101
- const [copied, setCopied] = useState(false);
102
- const rootRef = useRef<HTMLDivElement>(null);
103
-
104
- // Dismiss on outside click or Escape — standard popover behaviour, and it
105
- // keeps the badge from trapping focus on a reviewer's own page.
106
- useEffect(() => {
107
- if (!open) return;
108
- const onPointerDown = (e: MouseEvent) => {
109
- if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);
110
- };
111
- const onKeyDown = (e: KeyboardEvent) => {
112
- if (e.key === "Escape") setOpen(false);
113
- };
114
- document.addEventListener("mousedown", onPointerDown);
115
- document.addEventListener("keydown", onKeyDown);
116
- return () => {
117
- document.removeEventListener("mousedown", onPointerDown);
118
- document.removeEventListener("keydown", onKeyDown);
119
- };
120
- }, [open]);
121
-
122
- const exitPreview = useCallback(() => {
123
- window.location.href = buildExitUrl(window.location.href);
124
- }, []);
125
-
126
- const sharePreviewLink = useCallback(async () => {
127
- const link = buildShareUrl(window.location.href, pointer);
128
- try {
129
- await navigator.clipboard.writeText(link);
130
- setCopied(true);
131
- window.setTimeout(() => setCopied(false), 2000);
132
- } catch {
133
- // Clipboard blocked (insecure context / permissions): fall back to a
134
- // prompt so the reviewer can still copy the link by hand rather than
135
- // silently doing nothing.
136
- window.prompt("Copy preview link:", link);
137
- }
138
- }, [pointer]);
139
-
140
- return (
141
- <div
142
- ref={rootRef}
143
- data-deco-preview-badge=""
144
- style={{
145
- position: "fixed",
146
- bottom: 16,
147
- left: 16,
148
- zIndex: 2147483647,
149
- fontFamily:
150
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
151
- fontSize: 13,
152
- lineHeight: 1.4,
153
- }}
154
- >
155
- {open ? (
156
- <div
157
- role="menu"
158
- style={{
159
- position: "absolute",
160
- bottom: "calc(100% + 8px)",
161
- left: 0,
162
- minWidth: 200,
163
- background: "#fff",
164
- color: "#1a1a1a",
165
- borderRadius: 12,
166
- boxShadow: "0 8px 30px rgba(0,0,0,0.18), 0 0 0 1px rgba(0,0,0,0.05)",
167
- overflow: "hidden",
168
- }}
169
- >
170
- <MenuItem onClick={exitPreview} label="Exit preview" icon={<ExitIcon />} />
171
- <div style={{ height: 1, background: "rgba(0,0,0,0.07)" }} />
172
- <MenuItem
173
- onClick={sharePreviewLink}
174
- label={copied ? "Copied!" : "Share preview"}
175
- icon={<ShareIcon />}
176
- />
177
- </div>
178
- ) : null}
179
-
180
- <button
181
- type="button"
182
- aria-expanded={open}
183
- aria-label="Preview mode"
184
- onClick={() => setOpen((v) => !v)}
185
- style={{
186
- display: "inline-flex",
187
- alignItems: "center",
188
- gap: 8,
189
- padding: "7px 12px 7px 8px",
190
- background: DECO_LIME,
191
- color: DECO_GREEN,
192
- border: "none",
193
- borderRadius: 9999,
194
- cursor: "pointer",
195
- fontSize: 13,
196
- fontWeight: 600,
197
- boxShadow: "0 4px 14px rgba(0,0,0,0.25)",
198
- }}
199
- >
200
- <img
201
- src={DECO_MARK_DATA_URI}
202
- alt=""
203
- width={18}
204
- height={18}
205
- style={{ display: "block", borderRadius: 4 }}
206
- />
207
- <span>Preview mode</span>
208
- </button>
209
- </div>
210
- );
211
- }
212
-
213
- function MenuItem({
214
- onClick,
215
- label,
216
- icon,
217
- }: {
218
- onClick: () => void;
219
- label: string;
220
- icon: ReactNode;
221
- }) {
222
- return (
223
- <button
224
- type="button"
225
- role="menuitem"
226
- onClick={onClick}
227
- style={{
228
- display: "flex",
229
- alignItems: "center",
230
- gap: 10,
231
- width: "100%",
232
- textAlign: "left",
233
- padding: "10px 14px",
234
- background: "transparent",
235
- border: "none",
236
- color: "inherit",
237
- cursor: "pointer",
238
- fontSize: 13,
239
- whiteSpace: "nowrap",
240
- }}
241
- onMouseEnter={(e) => {
242
- e.currentTarget.style.background = "rgba(0,0,0,0.05)";
243
- }}
244
- onMouseLeave={(e) => {
245
- e.currentTarget.style.background = "transparent";
246
- }}
247
- >
248
- <span style={{ display: "inline-flex", flexShrink: 0, color: DECO_GREEN }}>{icon}</span>
249
- <span>{label}</span>
250
- </button>
251
- );
252
- }
11
+ export {
12
+ buildExitUrl,
13
+ buildShareUrl,
14
+ DraftPreviewBadge,
15
+ type DraftPreviewBadgeProps,
16
+ } from "@decocms/blocks/preview";
@@ -0,0 +1,66 @@
1
+ // @vitest-environment node
2
+ /**
3
+ * Conformance: the Next.js half of caching.mdx ("Upstream data": requests go
4
+ * through Next's data cache, no cache of the binding's own) and of
5
+ * telemetry.mdx ("What's sent": the bindings add no measurements of their own;
6
+ * inbound requests come from Next's own OpenTelemetry setup).
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { createInstrumentedFetch } from "@decocms/blocks/fetch";
12
+ import { afterEach, describe, expect, it, vi } from "vitest";
13
+
14
+ const SRC = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
+
16
+ function sources(): { file: string; text: string }[] {
17
+ const out: { file: string; text: string }[] = [];
18
+ const walk = (dir: string) => {
19
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
20
+ const full = path.join(dir, entry.name);
21
+ if (entry.isDirectory()) {
22
+ if (entry.name !== "__conformance__" && entry.name !== "__tests__") walk(full);
23
+ } else if (/\.(ts|tsx|cjs|js)$/.test(entry.name) && !/\.test\./.test(entry.name)) {
24
+ out.push({ file: path.relative(SRC, full), text: fs.readFileSync(full, "utf8") });
25
+ }
26
+ }
27
+ };
28
+ walk(SRC);
29
+ return out;
30
+ }
31
+
32
+ afterEach(() => {
33
+ vi.unstubAllGlobals();
34
+ });
35
+
36
+ describe("upstream data on Next.js (caching.mdx)", () => {
37
+ it("cache-04: the instrumented fetch goes through the fetch Next patches, with Next's cache options intact", async () => {
38
+ const instrumented = createInstrumentedFetch({ provider: "acme-search" });
39
+ // Next replaces globalThis.fetch after modules load; the instrumented fetch must use it.
40
+ const nextFetch = vi.fn(async () => new Response("{}"));
41
+ vi.stubGlobal("fetch", nextFetch);
42
+ await instrumented("https://search.example/q", {
43
+ operation: "search",
44
+ next: { revalidate: 60, tags: ["search"] },
45
+ } as RequestInit & { operation: string });
46
+ expect(nextFetch).toHaveBeenCalledTimes(1);
47
+ const init = (nextFetch.mock.calls[0] as unknown as [unknown, Record<string, unknown>])[1];
48
+ expect(init.next).toEqual({ revalidate: 60, tags: ["search"] });
49
+ });
50
+
51
+ it("cache-04: @decocms/nextjs has no upstream cache of its own", () => {
52
+ const own = sources().filter(({ text }) =>
53
+ /caches\.default|caches\.open|createFetchCache|fetchWithCache/.test(text),
54
+ );
55
+ expect(own.map((s) => s.file)).toEqual([]);
56
+ });
57
+ });
58
+
59
+ describe("what the binding adds (telemetry.mdx › What's sent)", () => {
60
+ it("tel-09: @decocms/nextjs adds no measurements of its own (no inbound or page-cache metrics)", () => {
61
+ const reporters = sources().filter(({ text }) =>
62
+ /http\.server\.request\.duration|decocms\.blocks\.telemetry/.test(text),
63
+ );
64
+ expect(reporters.map((s) => s.file)).toEqual([]);
65
+ });
66
+ });
@@ -57,7 +57,7 @@ function buildSeo(page: DecoPageResult): PageSeo {
57
57
  *
58
58
  * `resolveForPath` is wrapped in React's `cache()` so `generateMetadata` and
59
59
  * the page body share one `resolveDecoPage` call per request instead of
60
- * resolving twice — the same pattern faststore-fila's own
60
+ * resolving twice — the same pattern a production Next.js site's own
61
61
  * `resolveCmsPageByPath` already used against the old /next tier. This relies
62
62
  * on Next's RSC renderer establishing a per-request cache boundary that
63
63
  * `cache()` memoizes against: calling the returned functions directly outside
package/src/draft.ts CHANGED
@@ -29,11 +29,11 @@ import {
29
29
  } from "@decocms/blocks/cms";
30
30
  import { cookies, headers } from "next/headers";
31
31
  import { cache } from "react";
32
- import { DRAFT_COOKIE, DRAFT_PARAM } from "./draftConstants";
32
+ import { DRAFT_COOKIE, DRAFT_HEADER, DRAFT_PARAM } from "./draftConstants";
33
33
 
34
34
  // Re-exported for back-compat: these used to live here, but the client badge
35
35
  // needs DRAFT_PARAM and cannot import this module (it pulls in `next/headers`).
36
- export { DRAFT_COOKIE, DRAFT_PARAM } from "./draftConstants";
36
+ export { DRAFT_COOKIE, DRAFT_HEADER, DRAFT_PARAM } from "./draftConstants";
37
37
 
38
38
  /**
39
39
  * Request-scoped slot.
@@ -124,15 +124,21 @@ export function selectDraftPointer(
124
124
  export async function ensureDraft(searchParams?: DraftSearchParams): Promise<boolean> {
125
125
  registerDraftOverride();
126
126
 
127
- const cookieStore = await cookies();
128
- const pointer = selectDraftPointer(searchParams, cookieStore.get(DRAFT_COOKIE)?.value);
127
+ const [cookieStore, requestHeaders] = await Promise.all([cookies(), headers()]);
128
+
129
+ // Pointer precedence: the page's own `?__draft=` param wins; then the
130
+ // middleware-forwarded header (the only source a LAYOUT can see on the entry
131
+ // request — see DRAFT_HEADER); then the navigation cookie. This is what lets
132
+ // `ensureDraft()` bind from a layout so shell-resolved Header/Footer reflect
133
+ // the draft, not just page sections.
134
+ const carried = requestHeaders.get(DRAFT_HEADER) ?? cookieStore.get(DRAFT_COOKIE)?.value ?? null;
135
+ const pointer = selectDraftPointer(searchParams, carried);
129
136
  if (!pointer) return false;
130
137
 
131
138
  // Host gate, checked only once a pointer exists (headers() is a dynamic
132
139
  // API): the same build may serve the preview domain and the production
133
140
  // domain, and only hosts named in DECO_ALLOWED_PREVIEW_HOSTS may render
134
141
  // drafts — production stays published no matter what the URL carries.
135
- const requestHeaders = await headers();
136
142
  const host = requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host");
137
143
  if (!isDraftHostAllowed(host)) return false;
138
144
 
@@ -13,3 +13,18 @@ export const DRAFT_PARAM = "__draft";
13
13
 
14
14
  /** Cookie that carries the pointer across in-preview navigation. */
15
15
  export const DRAFT_COOKIE = "__deco_draft";
16
+
17
+ /**
18
+ * Request header the middleware forwards the active pointer on, so the whole
19
+ * RSC tree — including the root layout / app shell — can bind the draft on the
20
+ * SAME request. The page reads `?__draft=` from its own `searchParams`, but a
21
+ * layout never receives `searchParams` and, on the entry request, the cookie
22
+ * has only just been set on the *response* (not yet readable). Forwarding the
23
+ * pointer as a request header closes both gaps so `ensureDraft()` works from a
24
+ * layout, letting shell-resolved Header/Footer reflect the draft too.
25
+ *
26
+ * Not a new trust surface: the pointer was never a secret (the draft id is the
27
+ * capability, host-scoping bounds blast radius — see `draftSource`), so a
28
+ * forwarded/forged header is equivalent to typing the query param.
29
+ */
30
+ export const DRAFT_HEADER = "x-deco-draft";
@@ -2,7 +2,12 @@ import { NextRequest, NextResponse } from "next/server";
2
2
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
3
 
4
4
  import { DRAFT_COOKIE } from "./draft";
5
- import { applyDraft, prepareDraft, rewriteToDraftRoute } from "./draftMiddleware";
5
+ import {
6
+ applyDraft,
7
+ draftRequestHeaders,
8
+ prepareDraft,
9
+ rewriteToDraftRoute,
10
+ } from "./draftMiddleware";
6
11
 
7
12
  beforeEach(() => {
8
13
  // The middleware is host-gated; these tests run as the allowed host.
@@ -42,15 +47,18 @@ describe("prepareDraft", () => {
42
47
 
43
48
  it("is inert on a host outside the allowlist — the production domain", () => {
44
49
  // Same build, different Host: no cookie, no rewrite, nothing touched.
45
- const req = request("https://fila.com.br/p?__draft=abc@v1");
46
- process.env.DECO_ALLOWED_PREVIEW_HOSTS = "fila.vtex.app";
50
+ const req = request("https://acme.com.br/p?__draft=abc@v1");
51
+ process.env.DECO_ALLOWED_PREVIEW_HOSTS = "acme.vtex.app";
47
52
  expect(prepareDraft(req)).toEqual({ pointer: null, setCookie: null, clearCookie: false });
48
53
  expect(rewriteToDraftRoute(req, prepareDraft(req))).toBeNull();
49
54
  });
50
55
 
51
- it("ignores a client-supplied draft header — the page owns the decision", () => {
52
- // There is no request-header path any more; the page reads searchParams
53
- // and cookies itself, so a forged header is simply not an input.
56
+ it("does not read a client-supplied draft header — the decision is param/cookie only", () => {
57
+ // The forwarded `x-deco-draft` header (read by `ensureDraft` in the layout)
58
+ // is an OUTPUT of the middleware decision, never an input to it: prepareDraft
59
+ // still decides purely from the param + cookie. A client that forges the
60
+ // header directly can't manufacture a decision here (and `draftRequestHeaders`
61
+ // strips it when not previewing).
54
62
  const req = new NextRequest(new URL("https://site.example/p"), {
55
63
  headers: { "x-deco-draft": "attacker.localhost@v1" },
56
64
  });
@@ -58,6 +66,30 @@ describe("prepareDraft", () => {
58
66
  });
59
67
  });
60
68
 
69
+ describe("draftRequestHeaders", () => {
70
+ it("forwards the active pointer on x-deco-draft so a layout can bind it", () => {
71
+ const req = request("https://site.example/p?__draft=abc.localhost@v1");
72
+ const headers = draftRequestHeaders(req, prepareDraft(req));
73
+ expect(headers.get("x-deco-draft")).toBe("abc.localhost@v1");
74
+ });
75
+
76
+ it("forwards the cookie-carried pointer on plain navigation", () => {
77
+ const req = request("https://site.example/p", "abc.localhost@v1");
78
+ const headers = draftRequestHeaders(req, prepareDraft(req));
79
+ expect(headers.get("x-deco-draft")).toBe("abc.localhost@v1");
80
+ });
81
+
82
+ it("strips a client-forged header when the request is not previewing", () => {
83
+ // Direct-to-origin request carrying a forged header but no real pointer:
84
+ // the forwarded headers must not carry it downstream.
85
+ const req = new NextRequest(new URL("https://site.example/p"), {
86
+ headers: { "x-deco-draft": "attacker.localhost@v1" },
87
+ });
88
+ const headers = draftRequestHeaders(req, prepareDraft(req));
89
+ expect(headers.get("x-deco-draft")).toBeNull();
90
+ });
91
+ });
92
+
61
93
  describe("applyDraft", () => {
62
94
  it("sets a partitioned cross-site cookie when entering draft mode", () => {
63
95
  const decision = prepareDraft(request("https://site.example/p?__draft=abc.localhost@v1"));
@@ -25,6 +25,7 @@ import { type NextRequest, NextResponse } from "next/server";
25
25
  import {
26
26
  DRAFT_COOKIE,
27
27
  DRAFT_COOKIE_OPTIONS,
28
+ DRAFT_HEADER,
28
29
  type DraftMiddlewareDecision,
29
30
  decideDraft,
30
31
  } from "./draft";
@@ -127,10 +128,36 @@ export function applyDraft(
127
128
  }
128
129
 
129
130
  /**
130
- * Convenience for sites with no middleware of their own: prepare, continue,
131
- * apply. Sites that already have middleware should call the two halves
132
- * directly so their own logic sits in between.
131
+ * Request headers to forward downstream, carrying the active pointer on
132
+ * `x-deco-draft` (see {@link DRAFT_HEADER}) so the RSC tree — including the app
133
+ * shell — can bind the draft via `ensureDraft()` and reflect it in
134
+ * shell-resolved Header/Footer, not only page sections.
135
+ *
136
+ * Pass to `NextResponse.next({ request: { headers } })`. Strips any
137
+ * client-supplied `x-deco-draft` when this request is not previewing, so the
138
+ * header is only ever present on a legitimately-gated preview request.
139
+ */
140
+ export function draftRequestHeaders(
141
+ request: NextRequest,
142
+ decision: DraftMiddlewareDecision,
143
+ ): Headers {
144
+ const headers = new Headers(request.headers);
145
+ if (decision.pointer) headers.set(DRAFT_HEADER, decision.pointer);
146
+ else headers.delete(DRAFT_HEADER);
147
+ return headers;
148
+ }
149
+
150
+ /**
151
+ * Convenience for sites with no middleware of their own: prepare, forward the
152
+ * pointer header, continue, apply. Sites that already have middleware should
153
+ * call the halves directly (`prepareDraft` → `draftRequestHeaders` on the
154
+ * `NextResponse.next({ request: { headers } })` → `applyDraft`) so their own
155
+ * logic sits in between.
133
156
  */
134
157
  export function draftMiddleware(request: NextRequest): NextResponse {
135
- return applyDraft(NextResponse.next(), prepareDraft(request));
158
+ const decision = prepareDraft(request);
159
+ const response = NextResponse.next({
160
+ request: { headers: draftRequestHeaders(request, decision) },
161
+ });
162
+ return applyDraft(response, decision);
136
163
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `ensureDraft()` must bind from the middleware-forwarded `x-deco-draft` header
3
+ * — the only pointer source a LAYOUT (app shell) can see: it receives no
4
+ * `searchParams`, and on the entry request the cookie has only just been set on
5
+ * the response. This is what lets shell-resolved Header/Footer reflect a draft,
6
+ * not just page sections.
7
+ */
8
+ import { clearDraftCache } from "@decocms/blocks/cms";
9
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
10
+
11
+ // Mutable header/cookie state the mock reads, so each test drives its own.
12
+ let headerState = new Headers();
13
+ let cookieState: Record<string, string> = {};
14
+
15
+ vi.mock("next/headers", () => ({
16
+ headers: async () => headerState,
17
+ cookies: async () => ({
18
+ get: (name: string) =>
19
+ name in cookieState ? { name, value: cookieState[name] } : undefined,
20
+ }),
21
+ }));
22
+
23
+ const { ensureDraft, DRAFT_HEADER, DRAFT_COOKIE } = await import("./draft");
24
+
25
+ const DRAFT_BLOCKS = { "pages-home": { title: "DRAFT" } };
26
+ const POINTER = "abc.localhost:60534/api/o/decofile/m/main?token=t@v1";
27
+
28
+ beforeEach(() => {
29
+ headerState = new Headers({ host: "site.example" });
30
+ cookieState = {};
31
+ process.env.DECO_ALLOWED_PREVIEW_HOSTS = "site.example";
32
+ clearDraftCache();
33
+ vi.stubGlobal(
34
+ "fetch",
35
+ vi.fn(async (url: string) => {
36
+ if (
37
+ String(url) ===
38
+ "http://abc.localhost:60534/api/o/decofile/m/main?token=t"
39
+ ) {
40
+ return new Response(JSON.stringify(DRAFT_BLOCKS), {
41
+ status: 200,
42
+ headers: { "content-type": "application/json" },
43
+ });
44
+ }
45
+ throw new Error(`unexpected fetch: ${url}`);
46
+ }),
47
+ );
48
+ });
49
+
50
+ afterEach(() => {
51
+ vi.unstubAllGlobals();
52
+ delete process.env.DECO_ALLOWED_PREVIEW_HOSTS;
53
+ });
54
+
55
+ describe("ensureDraft — app-shell (layout) binding via the forwarded header", () => {
56
+ it("binds the draft from x-deco-draft with no searchParams (the layout case)", async () => {
57
+ headerState.set(DRAFT_HEADER, POINTER);
58
+ // No searchParams (layouts get none) and no cookie (entry request).
59
+ expect(await ensureDraft()).toBe(true);
60
+ });
61
+
62
+ it("still binds from the cookie when there is no forwarded header", async () => {
63
+ cookieState[DRAFT_COOKIE] = POINTER;
64
+ expect(await ensureDraft()).toBe(true);
65
+ });
66
+
67
+ it("does not bind when neither header, cookie, nor param is present", async () => {
68
+ expect(await ensureDraft()).toBe(false);
69
+ });
70
+
71
+ it("stays gated by the host allowlist even with a forwarded header", async () => {
72
+ headerState = new Headers({ host: "prod.example" });
73
+ headerState.set(DRAFT_HEADER, POINTER);
74
+ expect(await ensureDraft()).toBe(false);
75
+ });
76
+ });
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export { DraftPreviewIndicator } from "./DraftPreviewIndicator";
18
18
  export {
19
19
  DRAFT_COOKIE,
20
20
  DRAFT_COOKIE_OPTIONS,
21
+ DRAFT_HEADER,
21
22
  DRAFT_PARAM,
22
23
  type DraftMiddlewareDecision,
23
24
  type DraftSearchParams,
@@ -30,6 +31,7 @@ export {
30
31
  export {
31
32
  applyDraft,
32
33
  DRAFT_ROUTE_PREFIX,
34
+ draftRequestHeaders,
33
35
  prepareDraft,
34
36
  rewriteToDraftRoute,
35
37
  } from "./draftMiddleware";
@@ -57,7 +57,7 @@ function preflight(request: Request): Response {
57
57
 
58
58
  /** For app/live/_meta/route.ts: `export { metaGET as GET } from "@decocms/nextjs/routeHandlers"` */
59
59
  export async function metaGET(request: Request): Promise<Response> {
60
- return withCors(request, handleMeta(request));
60
+ return withCors(request, await handleMeta(request));
61
61
  }
62
62
 
63
63
  /** For app/.decofile/route.ts (or an equivalent rewritten path — Next.js route
@@ -1,16 +0,0 @@
1
- import { renderToString } from "react-dom/server";
2
- import { describe, expect, it } from "vitest";
3
-
4
- import { DraftPreviewBadge } from "./DraftPreviewBadge";
5
-
6
- describe("DraftPreviewBadge (render)", () => {
7
- it("surfaces the preview-mode label so a reviewer can't mistake a draft for live", () => {
8
- const html = renderToString(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
9
- expect(html).toContain("Preview mode");
10
- });
11
-
12
- it("embeds the deco mark inline (no external asset that could 404 on a consumer site)", () => {
13
- const html = renderToString(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
14
- expect(html).toContain("data:image/png;base64,");
15
- });
16
- });
@@ -1,37 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { buildExitUrl, buildShareUrl } from "./DraftPreviewBadge";
4
-
5
- describe("buildShareUrl", () => {
6
- it("pins the exact draft version so a recipient sees the same working tree", () => {
7
- expect(buildShareUrl("https://fila.vtex.app/pdp/shoe", "abc.localhost:60534@v7")).toBe(
8
- "https://fila.vtex.app/pdp/shoe?__draft=abc.localhost%3A60534%40v7",
9
- );
10
- });
11
-
12
- it("preserves existing query params, only setting the draft token", () => {
13
- expect(buildShareUrl("https://fila.vtex.app/plp?sort=price", "h@v1")).toBe(
14
- "https://fila.vtex.app/plp?sort=price&__draft=h%40v1",
15
- );
16
- });
17
-
18
- it("replaces a stale token already in the URL rather than appending a second", () => {
19
- expect(buildShareUrl("https://fila.vtex.app/p?__draft=h@old", "h@new")).toBe(
20
- "https://fila.vtex.app/p?__draft=h%40new",
21
- );
22
- });
23
- });
24
-
25
- describe("buildExitUrl", () => {
26
- it("sets the ?__draft=off exit sentinel", () => {
27
- expect(buildExitUrl("https://fila.vtex.app/pdp/shoe")).toBe(
28
- "https://fila.vtex.app/pdp/shoe?__draft=off",
29
- );
30
- });
31
-
32
- it("overrides an active token with off (leaves preview from anywhere)", () => {
33
- expect(buildExitUrl("https://fila.vtex.app/p?__draft=h@v1")).toBe(
34
- "https://fila.vtex.app/p?__draft=off",
35
- );
36
- });
37
- });
package/src/decoMark.ts DELETED
@@ -1,11 +0,0 @@
1
- /**
2
- * Deco brand mark, inlined as a data URI so the draft-preview badge is fully
3
- * self-contained: it renders inside an arbitrary consumer site with no asset
4
- * pipeline, no `<img src>` that could 404, and no dependency on the site
5
- * shipping the logo. Only loaded when a draft is active (the badge is inert
6
- * otherwise), so the ~21KB cost never touches ordinary production traffic.
7
- *
8
- * Source: the deco "d↗" logo (pos-d-logo). Swap this constant to rebrand.
9
- */
10
- export const DECO_MARK_DATA_URI =
11
- "data:image/png;base64,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";