@decocms/nextjs 8.6.0 → 8.7.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 +2 -2
- package/dist/DraftPreviewIndicator.d.ts.map +1 -1
- package/dist/DraftPreviewIndicator.js +4 -3
- package/dist/DraftPreviewIndicator.js.map +1 -1
- package/dist/draft.d.ts +29 -5
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +31 -7
- package/dist/draft.js.map +1 -1
- package/dist/drafts.d.ts +11 -1
- package/dist/drafts.d.ts.map +1 -1
- package/dist/drafts.js +23 -3
- package/dist/drafts.js.map +1 -1
- package/dist/proxy/index.d.ts +51 -15
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +92 -23
- package/dist/proxy/index.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +19 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/slot.d.ts +6 -0
- package/dist/server/slot.d.ts.map +1 -1
- package/dist/server/slot.js +11 -1
- package/dist/server/slot.js.map +1 -1
- package/package.json +2 -2
- package/src/DraftPreviewIndicator.tsx +4 -3
- package/src/config/index.cjs +1 -1
- package/src/draft.ts +36 -7
- package/src/drafts.ts +35 -2
- package/src/proxy/index.ts +127 -28
- package/src/server/index.ts +27 -6
- package/src/server/slot.ts +16 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ export default async function DraftPage({ params, searchParams }) {
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Published pages stay cached (ISR, below): the proxy sends only
|
|
55
|
+
Published pages stay cached (ISR, below): the proxy sends only preview requests to the draft route (any `?__draft=`, `off` included, or the draft cookie; never the assignments route), sets the draft cookie, forwards the accepted version on `x-deco-version` (removing any copy sent from outside), and marks the response exactly `Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary: Cookie` and `X-Robots-Tag: noindex, nofollow`. A draft request checks for the latest release once (`cms.checkRelease()`) and forwards the check's id on `x-deco-release-check` (removing any copy sent from outside): the proxy's host check and read of the draft, `bindDraft`'s host check and the page's `contentClient` all wait for that one check, so one preview request fetches `latest.json` once (a page in another process than the proxy makes one check of its own). It reads a link's draft before forwarding it: a draft that can't be read anymore renders the release, expires the cookie and tells the pages on `x-deco-draft-unavailable`, so `DraftPreviewIndicator` shows a calm notice instead of an error page. A layout that reads content goes with its route, so the cached pages read no request: under the draft route (`app/%5Fdraft/layout.tsx`) it calls `bindDraft(cms)` and `contentClient(cms)`, which read the forwarded headers; under the assignments route (`app/%5Fassignments/[assignments]/layout.tsx`) it reads through `await assignedClient(cms, (await params).assignments)`. `bindDraft` reads `headers()`, and a cached page that reads a request header renders on every request instead, so never call it in the root layout or on the assignments route.
|
|
56
56
|
|
|
57
57
|
A draft link can carry a published release's commit SHA instead of a draft (Studio's "Preview this version"), and either one can carry a preview time (`__at`) and tests and campaigns switched on or off (`__test`, `__campaign`), which `cms.readVersion` reads as the client's `date`, `experiments` and `campaigns`. All of it passes the same host check and renders on the draft route: a visitor on the live site never picks a version. A preview may switch on a test at 0%.
|
|
58
58
|
|
|
@@ -155,7 +155,7 @@ const { withDeco } = require("@decocms/nextjs/config");
|
|
|
155
155
|
module.exports = withDeco({ images: { loader: "custom", loaderFile: "./src/image-loader.ts" } });
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
`withDeco` puts v7's two draft header rules before your own (a `?__draft=` query, or the draft cookie: no-
|
|
158
|
+
`withDeco` puts v7's two draft header rules before your own (a `?__draft=` query, or the draft cookie: `max-age=0, no-cache, must-revalidate`, `Vary: Cookie`, noindex) and adds Studio's origins to `allowedDevOrigins`. Each can be turned off: `withDeco(config, { draftHeaders: false, studioOrigins: false })`. Next sets `Cache-Control` and `Vary` itself on dynamic pages, so only `X-Robots-Tag` is sure to arrive from these rules; the proxy's `applyDraft` sets all three on the responses it makes.
|
|
159
159
|
|
|
160
160
|
## Request metric
|
|
161
161
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DraftPreviewIndicator.d.ts","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DraftPreviewIndicator.d.ts","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,0BAA0B;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAE,0BAA+B,GAAG,SAAS,CAI9F"}
|
|
@@ -2,18 +2,19 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* The "Preview mode" badge for the request's draft: a Server Component that
|
|
4
4
|
* renders `DraftBadge` (`@decocms/blocks/preview`) only when `bindDraft`
|
|
5
|
-
* bound a draft, so a published page emits nothing
|
|
5
|
+
* bound a draft, so a published page emits nothing; when the link named a
|
|
6
|
+
* draft that can't be read anymore, the badge's calm notice instead.
|
|
6
7
|
*
|
|
7
8
|
* Render it inside the page, after `bindDraft` was awaited, never from a
|
|
8
9
|
* layout: a layout renders alongside the page that fills it, so it may run
|
|
9
10
|
* before the page bound its draft.
|
|
10
11
|
*/
|
|
11
12
|
import { DraftBadge } from "@decocms/blocks/preview";
|
|
12
|
-
import { activeDraftPointer } from "./server/slot.js";
|
|
13
|
+
import { activeDraftPointer, draftUnavailable } from "./server/slot.js";
|
|
13
14
|
export function DraftPreviewIndicator({ exitHref } = {}) {
|
|
14
15
|
const pointer = activeDraftPointer();
|
|
15
16
|
if (!pointer)
|
|
16
|
-
return null;
|
|
17
|
+
return draftUnavailable() ? _jsx(DraftBadge, { pointer: null, unavailable: true }) : null;
|
|
17
18
|
return _jsx(DraftBadge, { pointer: pointer, exitHref: exitHref });
|
|
18
19
|
}
|
|
19
20
|
//# sourceMappingURL=DraftPreviewIndicator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DraftPreviewIndicator.js","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"DraftPreviewIndicator.js","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAOxE,MAAM,UAAU,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAA+B,EAAE;IACjF,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,IAAI,CAAC,OAAO;QAAE,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC,KAAC,UAAU,IAAC,OAAO,EAAE,IAAI,EAAE,WAAW,SAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3F,OAAO,KAAC,UAAU,IAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAC;AAC9D,CAAC"}
|
package/dist/draft.d.ts
CHANGED
|
@@ -27,13 +27,37 @@ export declare const DRAFT_COOKIE = "__deco_draft";
|
|
|
27
27
|
* so a client can't send one itself.
|
|
28
28
|
*/
|
|
29
29
|
export declare const VERSION_HEADER = "x-deco-version";
|
|
30
|
+
/**
|
|
31
|
+
* The request header the proxy sets on a draft request it hands the pages:
|
|
32
|
+
* the id of the release check it made for it (`cms.checkRelease()`), so
|
|
33
|
+
* `bindDraft`'s host check and the page's draft reads wait for that check
|
|
34
|
+
* instead of making their own (one `latest.json` read per preview request).
|
|
35
|
+
* The proxy removes any copy that arrives from outside; an id the CMS didn't
|
|
36
|
+
* make (a proxy in another process) is a check of its own anyway.
|
|
37
|
+
*/
|
|
38
|
+
export declare const RELEASE_CHECK_HEADER = "x-deco-release-check";
|
|
30
39
|
/** The URL prefix a drafted request is rewritten onto (`app/%5Fdraft/[[...path]]`). */
|
|
31
40
|
export declare const DRAFT_ROUTE_PREFIX = "/_draft";
|
|
32
41
|
/**
|
|
33
|
-
* The
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
42
|
+
* The request header the proxy sets when the link named a draft that can't be
|
|
43
|
+
* read anymore (deleted, say): the pages render the release and
|
|
44
|
+
* `DraftPreviewIndicator` says so calmly. The proxy removes any copy that
|
|
45
|
+
* arrives from outside.
|
|
46
|
+
*/
|
|
47
|
+
export declare const DRAFT_UNAVAILABLE_HEADER = "x-deco-draft-unavailable";
|
|
48
|
+
/**
|
|
49
|
+
* The `Cache-Control` of every response to a preview request (a request with
|
|
50
|
+
* `?__draft=`, whatever its value, or the draft cookie): always revalidated,
|
|
51
|
+
* never answered from a cache (PO 2026-10-23).
|
|
52
|
+
*/
|
|
53
|
+
export declare const DRAFT_CACHE_CONTROL = "max-age=0, no-cache, must-revalidate";
|
|
54
|
+
/**
|
|
55
|
+
* The response headers of a preview: never answered from a browser's or a
|
|
56
|
+
* CDN's cache (`Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary:
|
|
57
|
+
* Cookie`) and never indexed (`X-Robots-Tag: noindex, nofollow`). `{}` when
|
|
58
|
+
* `preview` is `null`, `false` or empty, so the result of the draft decision
|
|
59
|
+
* applies as is: pass the version, or whether the request carries a draft
|
|
60
|
+
* signal at all (`?__draft=off` and a refused or unavailable draft too).
|
|
37
61
|
*/
|
|
38
|
-
export declare function draftResponseHeaders(
|
|
62
|
+
export declare function draftResponseHeaders(preview: string | boolean | null | undefined): Record<string, string>;
|
|
39
63
|
//# sourceMappingURL=draft.d.ts.map
|
package/dist/draft.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,6BAA6B,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,GAC3C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB"}
|
package/dist/draft.js
CHANGED
|
@@ -27,19 +27,43 @@ export const DRAFT_COOKIE = "__deco_draft";
|
|
|
27
27
|
* so a client can't send one itself.
|
|
28
28
|
*/
|
|
29
29
|
export const VERSION_HEADER = "x-deco-version";
|
|
30
|
+
/**
|
|
31
|
+
* The request header the proxy sets on a draft request it hands the pages:
|
|
32
|
+
* the id of the release check it made for it (`cms.checkRelease()`), so
|
|
33
|
+
* `bindDraft`'s host check and the page's draft reads wait for that check
|
|
34
|
+
* instead of making their own (one `latest.json` read per preview request).
|
|
35
|
+
* The proxy removes any copy that arrives from outside; an id the CMS didn't
|
|
36
|
+
* make (a proxy in another process) is a check of its own anyway.
|
|
37
|
+
*/
|
|
38
|
+
export const RELEASE_CHECK_HEADER = "x-deco-release-check";
|
|
30
39
|
/** The URL prefix a drafted request is rewritten onto (`app/%5Fdraft/[[...path]]`). */
|
|
31
40
|
export const DRAFT_ROUTE_PREFIX = "/_draft";
|
|
32
41
|
/**
|
|
33
|
-
* The
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
42
|
+
* The request header the proxy sets when the link named a draft that can't be
|
|
43
|
+
* read anymore (deleted, say): the pages render the release and
|
|
44
|
+
* `DraftPreviewIndicator` says so calmly. The proxy removes any copy that
|
|
45
|
+
* arrives from outside.
|
|
46
|
+
*/
|
|
47
|
+
export const DRAFT_UNAVAILABLE_HEADER = "x-deco-draft-unavailable";
|
|
48
|
+
/**
|
|
49
|
+
* The `Cache-Control` of every response to a preview request (a request with
|
|
50
|
+
* `?__draft=`, whatever its value, or the draft cookie): always revalidated,
|
|
51
|
+
* never answered from a cache (PO 2026-10-23).
|
|
52
|
+
*/
|
|
53
|
+
export const DRAFT_CACHE_CONTROL = "max-age=0, no-cache, must-revalidate";
|
|
54
|
+
/**
|
|
55
|
+
* The response headers of a preview: never answered from a browser's or a
|
|
56
|
+
* CDN's cache (`Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary:
|
|
57
|
+
* Cookie`) and never indexed (`X-Robots-Tag: noindex, nofollow`). `{}` when
|
|
58
|
+
* `preview` is `null`, `false` or empty, so the result of the draft decision
|
|
59
|
+
* applies as is: pass the version, or whether the request carries a draft
|
|
60
|
+
* signal at all (`?__draft=off` and a refused or unavailable draft too).
|
|
37
61
|
*/
|
|
38
|
-
export function draftResponseHeaders(
|
|
39
|
-
if (!
|
|
62
|
+
export function draftResponseHeaders(preview) {
|
|
63
|
+
if (!preview)
|
|
40
64
|
return {};
|
|
41
65
|
return {
|
|
42
|
-
"Cache-Control":
|
|
66
|
+
"Cache-Control": DRAFT_CACHE_CONTROL,
|
|
43
67
|
Vary: "Cookie",
|
|
44
68
|
"X-Robots-Tag": "noindex, nofollow",
|
|
45
69
|
};
|
package/dist/draft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,uFAAuF;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D,uFAAuF;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,sCAAsC,CAAC;AAE1E;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA4C;IAE5C,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO;QACL,eAAe,EAAE,mBAAmB;QACpC,IAAI,EAAE,QAAQ;QACd,cAAc,EAAE,mBAAmB;KACpC,CAAC;AACJ,CAAC"}
|
package/dist/drafts.d.ts
CHANGED
|
@@ -46,7 +46,17 @@ export declare function requestLike(headers: Headers, pathAndQuery: string, fall
|
|
|
46
46
|
* the preview's moment and switched tests and campaigns), or `null` for the
|
|
47
47
|
* current release: what its Studio link carries, on a host the CMS allows.
|
|
48
48
|
*/
|
|
49
|
-
export declare function allowedDraft(cms: Pick<CMS, "readVersion">, request: RequestLike): Promise<RequestVersion | null>;
|
|
49
|
+
export declare function allowedDraft(cms: Pick<CMS, "readVersion">, request: RequestLike, releaseCheck?: string | null): Promise<RequestVersion | null>;
|
|
50
|
+
/** Whether a version names a saved draft (a pointer, not a release's SHA and not `local`). */
|
|
51
|
+
export declare function namesDraft(version: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The release check of a request whose link names a draft
|
|
54
|
+
* (`cms.checkRelease()`), so its host check, its draft reads and its render
|
|
55
|
+
* share one read of `latest.json`: the one `forwarded` names when the CMS
|
|
56
|
+
* made it (the proxy's, for the same request), else a new one. `undefined`
|
|
57
|
+
* for any other request: the release, a release's SHA, no link.
|
|
58
|
+
*/
|
|
59
|
+
export declare function draftReleaseCheck(cms: Pick<CMS, "checkRelease">, request: RequestLike, forwarded?: string | null): Promise<string | undefined>;
|
|
50
60
|
/**
|
|
51
61
|
* The draft cookie: the same `Set-Cookie` `cms.draftCookie` writes (a test
|
|
52
62
|
* holds the two together). Studio frames the site cross-site, hence
|
package/dist/drafts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../src/drafts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../src/drafts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,KAAK,GAAG,EAGR,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AA2BzB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,GAAG,IAAI,CAM5E;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAI5D;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,CAIxF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,WAAW,GAAG,IAAI,CAQpB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAC7B,OAAO,EAAE,WAAW,EACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAIhC;AAKD,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAC9B,OAAO,EAAE,WAAW,EACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI7B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,EAAO,GAAG,MAAM,CAUvE;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAGlE"}
|
package/dist/drafts.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* No React, no Next.js and no module state: the proxy and the pages share it.
|
|
23
23
|
*/
|
|
24
|
-
import { parseVersion } from "@decocms/blocks";
|
|
24
|
+
import { parseDraftPointer, parseVersion, } from "@decocms/blocks";
|
|
25
25
|
import { DRAFT_COOKIE, DRAFT_PARAM } from "./draft.js";
|
|
26
26
|
const DRAFT_OFF = "off";
|
|
27
27
|
function draftParam(request) {
|
|
@@ -94,11 +94,31 @@ export function requestLike(headers, pathAndQuery, fallbackHost) {
|
|
|
94
94
|
* the preview's moment and switched tests and campaigns), or `null` for the
|
|
95
95
|
* current release: what its Studio link carries, on a host the CMS allows.
|
|
96
96
|
*/
|
|
97
|
-
export async function allowedDraft(cms, request) {
|
|
97
|
+
export async function allowedDraft(cms, request, releaseCheck) {
|
|
98
98
|
const read = readDraftVersion(request);
|
|
99
99
|
if (read === null)
|
|
100
100
|
return null;
|
|
101
|
-
return (await cms.readVersion(request)) === null ? null : read;
|
|
101
|
+
return (await cms.readVersion(request, { releaseCheck })) === null ? null : read;
|
|
102
|
+
}
|
|
103
|
+
/** A draft pointer's version that names no draft: the release, nothing fetched. */
|
|
104
|
+
const LOCAL_VERSION = "local";
|
|
105
|
+
/** Whether a version names a saved draft (a pointer, not a release's SHA and not `local`). */
|
|
106
|
+
export function namesDraft(version) {
|
|
107
|
+
const parsed = parseDraftPointer(version);
|
|
108
|
+
return parsed !== null && parsed.version !== LOCAL_VERSION;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The release check of a request whose link names a draft
|
|
112
|
+
* (`cms.checkRelease()`), so its host check, its draft reads and its render
|
|
113
|
+
* share one read of `latest.json`: the one `forwarded` names when the CMS
|
|
114
|
+
* made it (the proxy's, for the same request), else a new one. `undefined`
|
|
115
|
+
* for any other request: the release, a release's SHA, no link.
|
|
116
|
+
*/
|
|
117
|
+
export async function draftReleaseCheck(cms, request, forwarded) {
|
|
118
|
+
const read = readDraftVersion(request);
|
|
119
|
+
if (read === null || !namesDraft(read.version))
|
|
120
|
+
return undefined;
|
|
121
|
+
return cms.checkRelease(forwarded);
|
|
102
122
|
}
|
|
103
123
|
/**
|
|
104
124
|
* The draft cookie: the same `Set-Cookie` `cms.draftCookie` writes (a test
|
package/dist/drafts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../src/drafts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../src/drafts.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy/index.d.ts
CHANGED
|
@@ -30,8 +30,11 @@
|
|
|
30
30
|
* (`contentClient`), deciding nothing again. It sends the visitor's cookie on
|
|
31
31
|
* a first visit only, never from a preview.
|
|
32
32
|
*
|
|
33
|
-
* Cached (ISR) pages can't read a request, so a
|
|
34
|
-
*
|
|
33
|
+
* Cached (ISR) pages can't read a request, so a preview request (any draft
|
|
34
|
+
* signal: `?__draft=` with any value, `off` included, or the draft cookie) is
|
|
35
|
+
* rewritten onto a dynamic draft route, never cached, and answers
|
|
36
|
+
* `Cache-Control: max-age=0, no-cache, must-revalidate`; a drafted request
|
|
37
|
+
* is rewritten onto it (`/_draft/<path>`, mounted as
|
|
35
38
|
* `app/%5Fdraft/...`: a literal `_draft` folder is private in Next and never
|
|
36
39
|
* routes), and a published one onto the assignments route
|
|
37
40
|
* (`/_assignments/<assignments>/<path>`, `rewriteToAssignmentsRoute`), where
|
|
@@ -52,8 +55,11 @@ import { type Visitor } from "@decocms/blocks/experiments";
|
|
|
52
55
|
import { type NextRequest, NextResponse } from "next/server.js";
|
|
53
56
|
import { type RequestAssignments } from "../assignments.ts";
|
|
54
57
|
export { requestLike } from "../drafts.ts";
|
|
55
|
-
/**
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* What the draft decision needs from the CMS: its preview host check, the
|
|
60
|
+
* release, and the draft a link names (read once before the page renders).
|
|
61
|
+
*/
|
|
62
|
+
export type CMSHandle = Pick<CMS, "readVersion" | "forRelease" | "forDraft" | "checkRelease">;
|
|
57
63
|
/** What the proxy does with a request's draft state. */
|
|
58
64
|
export interface DraftDecision {
|
|
59
65
|
/**
|
|
@@ -65,13 +71,36 @@ export interface DraftDecision {
|
|
|
65
71
|
version: RequestVersion | null;
|
|
66
72
|
/** The `Set-Cookie` the response carries: storing a link's version, or ending a preview. */
|
|
67
73
|
setCookie: string | null;
|
|
74
|
+
/**
|
|
75
|
+
* The request carries a draft signal at all (`?__draft=` with any value, or
|
|
76
|
+
* the draft cookie): it renders on the draft route, never from a cache,
|
|
77
|
+
* and answers `draftResponseHeaders`, whatever `version` is.
|
|
78
|
+
*/
|
|
79
|
+
preview?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* The link named a draft that can't be read (deleted, or its host
|
|
82
|
+
* failing): the release renders, the cookie is expired when the draft is
|
|
83
|
+
* gone (a failure that may pass leaves it), and the pages show
|
|
84
|
+
* the calm notice (`x-deco-draft-unavailable`), never an error page.
|
|
85
|
+
*/
|
|
86
|
+
unavailable?: true;
|
|
87
|
+
/**
|
|
88
|
+
* The release check made for this draft request (`cms.checkRelease()`),
|
|
89
|
+
* forwarded on `x-deco-release-check`: the host check, the draft read here
|
|
90
|
+
* and the page's all wait for it, so the request reads `latest.json` once.
|
|
91
|
+
*/
|
|
92
|
+
releaseCheck?: string;
|
|
68
93
|
}
|
|
69
94
|
export { ASSIGNMENTS_HEADER, ASSIGNMENTS_ROUTE_PREFIX, assignmentsKey, assignmentsRoutePath, assignmentsSegment, isAssignmentsRoutePath, type PathAssignments, parseAssignmentsSegment, type RequestAssignments, readAssignments, } from "../assignments.ts";
|
|
70
95
|
export { DRAFT_ROUTE_PREFIX, VERSION_HEADER } from "../draft.ts";
|
|
71
96
|
/**
|
|
72
97
|
* The version a request renders and the cookie its response sets, on a host
|
|
73
98
|
* the CMS allows (as the pages' `bindDraft` checks again). A request with
|
|
74
|
-
* no `?__draft=` and no draft cookie never reaches the CMS.
|
|
99
|
+
* no `?__draft=` and no draft cookie never reaches the CMS. A draft request
|
|
100
|
+
* checks for the latest release once (`cms.checkRelease()`), shared by the
|
|
101
|
+
* host check, the read below and the page. A draft pointer
|
|
102
|
+
* is read once here (`forDraft(...).revision()`, the read the page makes
|
|
103
|
+
* again): a draft that can't be read gives the release and `unavailable`.
|
|
75
104
|
*/
|
|
76
105
|
export declare function decideDraft(cms: CMSHandle, request: NextRequest | Request): Promise<DraftDecision>;
|
|
77
106
|
/**
|
|
@@ -107,8 +136,9 @@ export declare function visitorRequestHeaders(headers: Headers, decision: Visito
|
|
|
107
136
|
export declare function applyVisitor<R extends Response>(response: R, decision: VisitorDecision): R;
|
|
108
137
|
/**
|
|
109
138
|
* Each test and campaign on or off for this request, decided once:
|
|
110
|
-
* `cms.client({ visitor, ...version })` (the visitor,
|
|
111
|
-
* version, moment and switched tests and campaigns
|
|
139
|
+
* `cms.client({ visitor, ...version, releaseCheck })` (the visitor, the Studio
|
|
140
|
+
* link's version, moment and switched tests and campaigns, and the request's
|
|
141
|
+
* release check) resolving the saved
|
|
112
142
|
* `experiments` and `campaigns` blocks. Missing blocks, or content that
|
|
113
143
|
* doesn't load, give none, never an error.
|
|
114
144
|
*/
|
|
@@ -122,10 +152,12 @@ export declare function assignmentsRequestHeaders(headers: Headers, assignments:
|
|
|
122
152
|
/** Whether `pathname` is on the draft route: a site answers a direct hit there with its 404. */
|
|
123
153
|
export declare function isDraftRoutePath(pathname: string): boolean;
|
|
124
154
|
/**
|
|
125
|
-
* The rewrite of a
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
155
|
+
* The rewrite of a preview request (a drafted one, or any other with a draft
|
|
156
|
+
* signal: `off`, a refused host, a draft that can't be read) onto the draft
|
|
157
|
+
* route, which is dynamic and never cached, keeping its path and forwarding
|
|
158
|
+
* the version and, when given, the visitor (`decideVisitor`) and the
|
|
159
|
+
* assignments (`decideAssignments`); `null` when the request isn't a preview
|
|
160
|
+
* or is already on the draft route.
|
|
129
161
|
*/
|
|
130
162
|
export declare function rewriteToDraftRoute(request: NextRequest, decision: DraftDecision, visitor?: VisitorDecision, assignments?: RequestAssignments): NextResponse | null;
|
|
131
163
|
/**
|
|
@@ -143,7 +175,7 @@ export declare function rewriteToDraftRoute(request: NextRequest, decision: Draf
|
|
|
143
175
|
* minutes stale (the first request after that still gets it while Next
|
|
144
176
|
* renders it again), a banner switching on late, which is accepted.
|
|
145
177
|
*
|
|
146
|
-
* `null` for a
|
|
178
|
+
* `null` for a preview request (it renders on the draft route, never cached)
|
|
147
179
|
* and for a path already on the draft or assignments route. When the content
|
|
148
180
|
* saves tests or campaigns, the response is marked `Cache-Control: private,
|
|
149
181
|
* no-cache`: Next keeps one cached page per set of assignments, but the URL
|
|
@@ -154,11 +186,15 @@ export declare function rewriteToDraftRoute(request: NextRequest, decision: Draf
|
|
|
154
186
|
*/
|
|
155
187
|
export declare function rewriteToAssignmentsRoute(cms: Pick<CMS, "client">, request: NextRequest, draft: DraftDecision, visitor: VisitorDecision): Promise<NextResponse | null>;
|
|
156
188
|
/**
|
|
157
|
-
* The cookie and the response headers of a
|
|
158
|
-
*
|
|
189
|
+
* The cookie and the response headers of a preview: `Set-Cookie`, and for
|
|
190
|
+
* any request with a draft signal (`preview`, or a version)
|
|
191
|
+
* `Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary: Cookie` and
|
|
159
192
|
* `X-Robots-Tag: noindex, nofollow` (`draftResponseHeaders`). With the
|
|
160
193
|
* version in a cookie a draft shares its URL with the published page, so a
|
|
161
|
-
* cache keyed on the URL alone would serve it to visitors.
|
|
194
|
+
* cache keyed on the URL alone would serve it to visitors. The proxy answers
|
|
195
|
+
* before the page renders, so it can't hold the cookie back from a failed
|
|
196
|
+
* render; `decideDraft` reads the draft first instead, and a draft that
|
|
197
|
+
* can't be read expires the cookie.
|
|
162
198
|
*/
|
|
163
199
|
export declare function applyDraft<R extends Response>(response: R, decision: DraftDecision): R;
|
|
164
200
|
export interface DraftProxyOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy/index.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { readVisitor, serializeVisitor } from "@decocms/blocks/experiments";
|
|
2
2
|
import { NextResponse } from "next/server.js";
|
|
3
3
|
import { ASSIGNMENTS_HEADER, assignmentsRoutePath, assignmentsSegment, isAssignmentsRoutePath, readAssignmentsAndScope, } from "../assignments.js";
|
|
4
|
-
import { DRAFT_ROUTE_PREFIX, draftResponseHeaders, VERSION_HEADER } from "../draft.js";
|
|
5
|
-
import { allowedDraft, draftCookie, draftLinkValue, endsPreview, hasDraftSignal, requestLike, } from "../drafts.js";
|
|
4
|
+
import { DRAFT_ROUTE_PREFIX, DRAFT_UNAVAILABLE_HEADER, draftResponseHeaders, RELEASE_CHECK_HEADER, VERSION_HEADER, } from "../draft.js";
|
|
5
|
+
import { allowedDraft, draftCookie, draftLinkValue, draftReleaseCheck, endsPreview, hasDraftSignal, requestLike, } from "../drafts.js";
|
|
6
6
|
import { VISITOR_HEADER } from "../visitor.js";
|
|
7
7
|
export { requestLike } from "../drafts.js";
|
|
8
8
|
export { ASSIGNMENTS_HEADER, ASSIGNMENTS_ROUTE_PREFIX, assignmentsKey, assignmentsRoutePath, assignmentsSegment, isAssignmentsRoutePath, parseAssignmentsSegment, readAssignments, } from "../assignments.js";
|
|
9
9
|
export { DRAFT_ROUTE_PREFIX, VERSION_HEADER } from "../draft.js";
|
|
10
10
|
const INERT = Object.freeze({ version: null, setCookie: null });
|
|
11
|
+
/** A preview that renders the release (`off`, a refused host, a value that doesn't parse). */
|
|
12
|
+
const RELEASE_PREVIEW = Object.freeze({
|
|
13
|
+
version: null,
|
|
14
|
+
setCookie: null,
|
|
15
|
+
preview: true,
|
|
16
|
+
});
|
|
11
17
|
/**
|
|
12
18
|
* The version a request renders and the cookie its response sets, on a host
|
|
13
19
|
* the CMS allows (as the pages' `bindDraft` checks again). A request with
|
|
14
|
-
* no `?__draft=` and no draft cookie never reaches the CMS.
|
|
20
|
+
* no `?__draft=` and no draft cookie never reaches the CMS. A draft request
|
|
21
|
+
* checks for the latest release once (`cms.checkRelease()`), shared by the
|
|
22
|
+
* host check, the read below and the page. A draft pointer
|
|
23
|
+
* is read once here (`forDraft(...).revision()`, the read the page makes
|
|
24
|
+
* again): a draft that can't be read gives the release and `unavailable`.
|
|
15
25
|
*/
|
|
16
26
|
export async function decideDraft(cms, request) {
|
|
17
27
|
const url = new URL(request.url);
|
|
@@ -20,15 +30,55 @@ export async function decideDraft(cms, request) {
|
|
|
20
30
|
return INERT;
|
|
21
31
|
// Ending a preview is allowed on any host, so a cookie set earlier always goes.
|
|
22
32
|
if (endsPreview(like))
|
|
23
|
-
return {
|
|
24
|
-
|
|
33
|
+
return { ...RELEASE_PREVIEW, setCookie: expiredCookie() };
|
|
34
|
+
// One release check per draft request, shared below and by the page.
|
|
35
|
+
const releaseCheck = await draftReleaseCheck(cms, like);
|
|
36
|
+
const version = await allowedDraft(cms, like, releaseCheck);
|
|
25
37
|
if (version === null)
|
|
26
|
-
return
|
|
38
|
+
return RELEASE_PREVIEW;
|
|
39
|
+
if (releaseCheck !== undefined) {
|
|
40
|
+
try {
|
|
41
|
+
await cms.forDraft(version.version, { releaseCheck }).revision();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Gone (deleted): the preview ends. Any other failure (the drafts' host
|
|
45
|
+
// down, a timeout) leaves the cookie alone, so the next request tries again.
|
|
46
|
+
const setCookie = isDraftGone(error) ? expiredCookie() : null;
|
|
47
|
+
return { ...RELEASE_PREVIEW, setCookie, unavailable: true };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
27
50
|
const link = draftLinkValue(like);
|
|
28
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
version,
|
|
53
|
+
setCookie: link ? draftCookie(encodeURIComponent(link)) : null,
|
|
54
|
+
preview: true,
|
|
55
|
+
...(releaseCheck === undefined ? {} : { releaseCheck }),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether a draft read failed because the drafts' host no longer has the
|
|
60
|
+
* draft (`404`/`410`), as opposed to a failure that may pass (a `5xx`, a
|
|
61
|
+
* timeout, a network error). Reads the error and its causes.
|
|
62
|
+
*/
|
|
63
|
+
function isDraftGone(error) {
|
|
64
|
+
for (let e = error, depth = 0; e instanceof Error && depth < 5; e = e.cause, depth++) {
|
|
65
|
+
if (/draft changes: HTTP (404|410)\b/.test(e.message))
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/** The `Set-Cookie` that ends a preview. */
|
|
71
|
+
function expiredCookie() {
|
|
72
|
+
return draftCookie("", ["Max-Age=0"]);
|
|
29
73
|
}
|
|
30
74
|
/** The headers the proxy writes for the pages: never taken from outside. */
|
|
31
|
-
const OWN_HEADERS = [
|
|
75
|
+
const OWN_HEADERS = [
|
|
76
|
+
VERSION_HEADER,
|
|
77
|
+
VISITOR_HEADER,
|
|
78
|
+
ASSIGNMENTS_HEADER,
|
|
79
|
+
DRAFT_UNAVAILABLE_HEADER,
|
|
80
|
+
RELEASE_CHECK_HEADER,
|
|
81
|
+
];
|
|
32
82
|
/**
|
|
33
83
|
* The request headers to forward: the accepted version on `x-deco-version`,
|
|
34
84
|
* so the root layout can bind the draft on the same request (a layout gets no
|
|
@@ -43,6 +93,11 @@ export function draftRequestHeaders(request, decision) {
|
|
|
43
93
|
headers.delete(name);
|
|
44
94
|
if (decision.version)
|
|
45
95
|
headers.set(VERSION_HEADER, decision.version.version);
|
|
96
|
+
if (decision.version && decision.releaseCheck) {
|
|
97
|
+
headers.set(RELEASE_CHECK_HEADER, decision.releaseCheck);
|
|
98
|
+
}
|
|
99
|
+
if (decision.unavailable)
|
|
100
|
+
headers.set(DRAFT_UNAVAILABLE_HEADER, "1");
|
|
46
101
|
return headers;
|
|
47
102
|
}
|
|
48
103
|
/** Whether the visitor asked over https (`x-forwarded-proto` first, as a host in front reports it). */
|
|
@@ -84,13 +139,20 @@ export function applyVisitor(response, decision) {
|
|
|
84
139
|
}
|
|
85
140
|
/**
|
|
86
141
|
* Each test and campaign on or off for this request, decided once:
|
|
87
|
-
* `cms.client({ visitor, ...version })` (the visitor,
|
|
88
|
-
* version, moment and switched tests and campaigns
|
|
142
|
+
* `cms.client({ visitor, ...version, releaseCheck })` (the visitor, the Studio
|
|
143
|
+
* link's version, moment and switched tests and campaigns, and the request's
|
|
144
|
+
* release check) resolving the saved
|
|
89
145
|
* `experiments` and `campaigns` blocks. Missing blocks, or content that
|
|
90
146
|
* doesn't load, give none, never an error.
|
|
91
147
|
*/
|
|
92
148
|
export function decideAssignments(cms, draft, visitor) {
|
|
93
|
-
|
|
149
|
+
// A draft's read shares the request's release check (`decideDraft`), never one of its own.
|
|
150
|
+
const releaseCheck = draft.version ? draft.releaseCheck : undefined;
|
|
151
|
+
return readAssignmentsAndScope(cms, {
|
|
152
|
+
visitor: visitor.visitor,
|
|
153
|
+
...draft.version,
|
|
154
|
+
...(releaseCheck ? { releaseCheck } : {}),
|
|
155
|
+
}).then((read) => read.assignments);
|
|
94
156
|
}
|
|
95
157
|
/**
|
|
96
158
|
* The forwarded headers with the decided tests and campaigns on
|
|
@@ -116,13 +178,15 @@ export function isDraftRoutePath(pathname) {
|
|
|
116
178
|
return pathname === DRAFT_ROUTE_PREFIX || pathname.startsWith(`${DRAFT_ROUTE_PREFIX}/`);
|
|
117
179
|
}
|
|
118
180
|
/**
|
|
119
|
-
* The rewrite of a
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
181
|
+
* The rewrite of a preview request (a drafted one, or any other with a draft
|
|
182
|
+
* signal: `off`, a refused host, a draft that can't be read) onto the draft
|
|
183
|
+
* route, which is dynamic and never cached, keeping its path and forwarding
|
|
184
|
+
* the version and, when given, the visitor (`decideVisitor`) and the
|
|
185
|
+
* assignments (`decideAssignments`); `null` when the request isn't a preview
|
|
186
|
+
* or is already on the draft route.
|
|
123
187
|
*/
|
|
124
188
|
export function rewriteToDraftRoute(request, decision, visitor, assignments) {
|
|
125
|
-
if (!decision.version)
|
|
189
|
+
if (!decision.version && !decision.preview)
|
|
126
190
|
return null;
|
|
127
191
|
const url = request.nextUrl.clone();
|
|
128
192
|
if (isDraftRoutePath(url.pathname))
|
|
@@ -149,7 +213,7 @@ const ASSIGNED_CACHE_CONTROL = "private, no-cache";
|
|
|
149
213
|
* minutes stale (the first request after that still gets it while Next
|
|
150
214
|
* renders it again), a banner switching on late, which is accepted.
|
|
151
215
|
*
|
|
152
|
-
* `null` for a
|
|
216
|
+
* `null` for a preview request (it renders on the draft route, never cached)
|
|
153
217
|
* and for a path already on the draft or assignments route. When the content
|
|
154
218
|
* saves tests or campaigns, the response is marked `Cache-Control: private,
|
|
155
219
|
* no-cache`: Next keeps one cached page per set of assignments, but the URL
|
|
@@ -159,7 +223,7 @@ const ASSIGNED_CACHE_CONTROL = "private, no-cache";
|
|
|
159
223
|
* (`isAssignmentsRoutePath`).
|
|
160
224
|
*/
|
|
161
225
|
export async function rewriteToAssignmentsRoute(cms, request, draft, visitor) {
|
|
162
|
-
if (draft.version)
|
|
226
|
+
if (draft.version || draft.preview)
|
|
163
227
|
return null;
|
|
164
228
|
const url = request.nextUrl.clone();
|
|
165
229
|
if (isDraftRoutePath(url.pathname) || isAssignmentsRoutePath(url.pathname))
|
|
@@ -176,16 +240,21 @@ export async function rewriteToAssignmentsRoute(cms, request, draft, visitor) {
|
|
|
176
240
|
return response;
|
|
177
241
|
}
|
|
178
242
|
/**
|
|
179
|
-
* The cookie and the response headers of a
|
|
180
|
-
*
|
|
243
|
+
* The cookie and the response headers of a preview: `Set-Cookie`, and for
|
|
244
|
+
* any request with a draft signal (`preview`, or a version)
|
|
245
|
+
* `Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary: Cookie` and
|
|
181
246
|
* `X-Robots-Tag: noindex, nofollow` (`draftResponseHeaders`). With the
|
|
182
247
|
* version in a cookie a draft shares its URL with the published page, so a
|
|
183
|
-
* cache keyed on the URL alone would serve it to visitors.
|
|
248
|
+
* cache keyed on the URL alone would serve it to visitors. The proxy answers
|
|
249
|
+
* before the page renders, so it can't hold the cookie back from a failed
|
|
250
|
+
* render; `decideDraft` reads the draft first instead, and a draft that
|
|
251
|
+
* can't be read expires the cookie.
|
|
184
252
|
*/
|
|
185
253
|
export function applyDraft(response, decision) {
|
|
186
254
|
if (decision.setCookie)
|
|
187
255
|
response.headers.append("set-cookie", decision.setCookie);
|
|
188
|
-
|
|
256
|
+
const preview = decision.preview === true || decision.version !== null;
|
|
257
|
+
for (const [name, value] of Object.entries(draftResponseHeaders(preview))) {
|
|
189
258
|
response.headers.set(name, value);
|
|
190
259
|
}
|
|
191
260
|
return response;
|
|
@@ -204,7 +273,7 @@ export function draftProxy(cms, options = {}) {
|
|
|
204
273
|
}
|
|
205
274
|
const decision = await decideDraft(cms, request);
|
|
206
275
|
const visitor = decideVisitor(request, decision);
|
|
207
|
-
const onAssignmentsRoute = decision.version === null && assignments?.(request) === true;
|
|
276
|
+
const onAssignmentsRoute = decision.version === null && decision.preview !== true && assignments?.(request) === true;
|
|
208
277
|
const rewritten = onAssignmentsRoute
|
|
209
278
|
? await rewriteToAssignmentsRoute(cms, request, decision, visitor)
|
|
210
279
|
: null;
|