fontdue-js 3.0.0-alpha12 → 3.0.0-alpha14
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/.playwright-mcp/console-2026-06-15T09-14-00-118Z.log +84 -0
- package/.playwright-mcp/console-2026-06-15T09-25-42-726Z.log +2 -0
- package/.playwright-mcp/console-2026-06-15T09-25-47-707Z.log +1 -0
- package/.playwright-mcp/page-2026-06-15T09-14-01-054Z.yml +13 -0
- package/CHANGELOG.md +2 -2
- package/README.md +25 -50
- package/dist/__tests__/createFontdueFetch.test.js +33 -0
- package/dist/__tests__/networkFetch.test.js +81 -2
- package/dist/__tests__/nextAdapter.test.js +123 -72
- package/dist/__tests__/serverConfig.test.js +62 -0
- package/dist/components/ConfigContext.d.ts +3 -0
- package/dist/components/ConfigContext.js +5 -2
- package/dist/components/FontdueAdminToolbar/index.js +72 -16
- package/dist/components/FontdueProvider/index.server.d.ts +1 -0
- package/dist/components/FontdueProvider/index.server.js +10 -0
- package/dist/fontdue.css +59 -0
- package/dist/next/index.d.ts +1 -2
- package/dist/next/index.js +14 -6
- package/dist/next/registerSingleTenantResolver.d.ts +1 -0
- package/dist/next/registerSingleTenantResolver.js +35 -0
- package/dist/next/revalidate.js +1 -1
- package/dist/next/tenant.d.ts +4 -4
- package/dist/next/tenant.js +75 -80
- package/dist/preview/constants.d.ts +2 -0
- package/dist/preview/constants.js +20 -1
- package/dist/relay/environment.d.ts +2 -0
- package/dist/relay/environment.js +67 -38
- package/dist/relay/serverConfig.d.ts +3 -8
- package/dist/relay/serverConfig.js +69 -19
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +28 -15
- package/package.json +1 -1
- package/types/next-navigation.d.ts +4 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[ 597ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:25630
|
|
2
|
+
[ 747ms] Error: Switched to client rendering because the server rendering errored:
|
|
3
|
+
|
|
4
|
+
Relay: Missing @required value at path 'collection' in 'BuyButtonIDQuery'.
|
|
5
|
+
at handleFieldErrors (webpack-internal:///(ssr)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:36:19)
|
|
6
|
+
at handlePotentialSnapshotErrors (webpack-internal:///(ssr)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:70:5)
|
|
7
|
+
at handlePotentialSnapshotErrorsForState (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:120:5)
|
|
8
|
+
at useFragmentInternal (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:420:3)
|
|
9
|
+
at useFragmentInternal (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal.js:11:54)
|
|
10
|
+
at useLazyLoadQueryNode (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQueryNode.js:64:14)
|
|
11
|
+
at useLazyLoadQuery (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQuery.js:13:14)
|
|
12
|
+
at BuyButtonIDQueryRenderer (webpack-internal:///(ssr)/./node_modules/fontdue-js/dist/components/BuyButton/index.js:104:79)
|
|
13
|
+
at Object.react_stack_bottom_frame (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:292649)
|
|
14
|
+
at renderWithHooks (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:114080)
|
|
15
|
+
at renderElement (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:128752)
|
|
16
|
+
at retryNode (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:194584)
|
|
17
|
+
at performWork (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:215436)
|
|
18
|
+
at Immediate._onImmediate (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:98974)
|
|
19
|
+
at process.processImmediate (node:internal/timers:491:21)
|
|
20
|
+
at process.callbackTrampoline (node:internal/async_hooks:130:17)
|
|
21
|
+
[ 1442ms] Error: Relay: Missing @required value at path 'collection' in 'BuyButtonIDQuery'.
|
|
22
|
+
at handleFieldErrors (webpack-internal:///(app-pages-browser)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:36:19)
|
|
23
|
+
at handlePotentialSnapshotErrors (webpack-internal:///(app-pages-browser)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:70:5)
|
|
24
|
+
at handlePotentialSnapshotErrorsForState (webpack-internal:///(app-pages-browser)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:120:5)
|
|
25
|
+
at useFragmentInternal (webpack-internal:///(app-pages-browser)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:420:3)
|
|
26
|
+
at useFragmentInternal (webpack-internal:///(app-pages-browser)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal.js:11:54)
|
|
27
|
+
at useLazyLoadQueryNode (webpack-internal:///(app-pages-browser)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQueryNode.js:64:14)
|
|
28
|
+
at useLazyLoadQuery (webpack-internal:///(app-pages-browser)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQuery.js:13:14)
|
|
29
|
+
at BuyButtonIDQueryRenderer (webpack-internal:///(app-pages-browser)/./node_modules/fontdue-js/dist/components/BuyButton/index.js:116:79)
|
|
30
|
+
at Object.react_stack_bottom_frame (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:23584:20)
|
|
31
|
+
at renderWithHooks (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:6793:22)
|
|
32
|
+
at updateFunctionComponent (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:9247:19)
|
|
33
|
+
at beginWork (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:10858:18)
|
|
34
|
+
at runWithFiberInDEV (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:872:30)
|
|
35
|
+
at performUnitOfWork (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:15727:22)
|
|
36
|
+
at workLoopSync (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:15547:41)
|
|
37
|
+
at renderRootSync (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:15527:11)
|
|
38
|
+
at performWorkOnRoot (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:15034:44)
|
|
39
|
+
at performWorkOnRootViaSchedulerTask (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:16816:7)
|
|
40
|
+
at MessagePort.performWorkUntilDeadline (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js:45:48)
|
|
41
|
+
[ 217249ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/dev/hot-reloader/app/hot-reloader-app.js:196
|
|
42
|
+
[ 217808ms] [WARNING] [Fast Refresh] performing full reload because your application had an unrecoverable error @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/dev/hot-reloader/app/hot-reloader-app.js:113
|
|
43
|
+
[ 219163ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:25630
|
|
44
|
+
[ 730814ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:25630
|
|
45
|
+
[ 735697ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:25630
|
|
46
|
+
[ 735780ms] Error: Switched to client rendering because the server rendering errored:
|
|
47
|
+
|
|
48
|
+
Relay: Missing @required value at path 'collection' in 'BuyButtonIDQuery'.
|
|
49
|
+
at handleFieldErrors (webpack-internal:///(ssr)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:36:19)
|
|
50
|
+
at handlePotentialSnapshotErrors (webpack-internal:///(ssr)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:70:5)
|
|
51
|
+
at handlePotentialSnapshotErrorsForState (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:120:5)
|
|
52
|
+
at useFragmentInternal (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:420:3)
|
|
53
|
+
at useFragmentInternal (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal.js:11:54)
|
|
54
|
+
at useLazyLoadQueryNode (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQueryNode.js:64:14)
|
|
55
|
+
at useLazyLoadQuery (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQuery.js:13:14)
|
|
56
|
+
at BuyButtonIDQueryRenderer (webpack-internal:///(ssr)/./node_modules/fontdue-js/dist/components/BuyButton/index.js:104:79)
|
|
57
|
+
at Object.react_stack_bottom_frame (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:292649)
|
|
58
|
+
at renderWithHooks (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:114080)
|
|
59
|
+
at renderElement (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:128752)
|
|
60
|
+
at retryNode (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:194584)
|
|
61
|
+
at performWork (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:215436)
|
|
62
|
+
at Immediate._onImmediate (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:98974)
|
|
63
|
+
at process.processImmediate (node:internal/timers:491:21)
|
|
64
|
+
at process.callbackTrampoline (node:internal/async_hooks:130:17)
|
|
65
|
+
[ 747981ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.development.js:25630
|
|
66
|
+
[ 748069ms] Error: Switched to client rendering because the server rendering errored:
|
|
67
|
+
|
|
68
|
+
Relay: Missing @required value at path 'collection' in 'BuyButtonIDQuery'.
|
|
69
|
+
at handleFieldErrors (webpack-internal:///(ssr)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:36:19)
|
|
70
|
+
at handlePotentialSnapshotErrors (webpack-internal:///(ssr)/./node_modules/relay-runtime/lib/util/handlePotentialSnapshotErrors.js:70:5)
|
|
71
|
+
at handlePotentialSnapshotErrorsForState (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:120:5)
|
|
72
|
+
at useFragmentInternal (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal_CURRENT.js:420:3)
|
|
73
|
+
at useFragmentInternal (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useFragmentInternal.js:11:54)
|
|
74
|
+
at useLazyLoadQueryNode (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQueryNode.js:64:14)
|
|
75
|
+
at useLazyLoadQuery (webpack-internal:///(ssr)/./node_modules/react-relay/lib/relay-hooks/useLazyLoadQuery.js:13:14)
|
|
76
|
+
at BuyButtonIDQueryRenderer (webpack-internal:///(ssr)/./node_modules/fontdue-js/dist/components/BuyButton/index.js:104:79)
|
|
77
|
+
at Object.react_stack_bottom_frame (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:292649)
|
|
78
|
+
at renderWithHooks (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:114080)
|
|
79
|
+
at renderElement (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:128752)
|
|
80
|
+
at retryNode (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:194584)
|
|
81
|
+
at performWork (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:215436)
|
|
82
|
+
at Immediate._onImmediate (/Users/tom/code/fontdue/generaltype/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:2:98974)
|
|
83
|
+
at process.processImmediate (node:internal/timers:491:21)
|
|
84
|
+
at process.callbackTrampoline (node:internal/async_hooks:130:17)
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
[ 4008ms] [ERROR] Refused to get unsafe header "X-Sg-Cs" @ https://www.google.com/search?q=generaltype.fontdue.xuyz&oq=generaltype.fontdue.xuyz&gs_lcrp=EgZjaHJvbWUyBggAEEUYOdIBCDMwODhqMGo3qAIAsAIA&sourceid=chrome&ie=UTF-8&sei=HMUvapfSAv7Ii-gP_8yN6A0:68
|
|
2
|
+
[ 4008ms] [ERROR] Refused to get unsafe header "X-Sorry-Redirect" @ https://www.google.com/search?q=generaltype.fontdue.xuyz&oq=generaltype.fontdue.xuyz&gs_lcrp=EgZjaHJvbWUyBggAEEUYOdIBCDMwODhqMGo3qAIAsAIA&sourceid=chrome&ie=UTF-8&sei=HMUvapfSAv7Ii-gP_8yN6A0:68
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[ 5170ms] [VERBOSE] [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o @ https://www.fontdue.xyz/login?redirect_to=%2Fadmin:0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- generic [active] [ref=e1]:
|
|
2
|
+
- generic [ref=e6] [cursor=pointer]:
|
|
3
|
+
- button "Open Next.js Dev Tools" [ref=e7]:
|
|
4
|
+
- img [ref=e8]
|
|
5
|
+
- generic [ref=e11]:
|
|
6
|
+
- button "Open issues overlay" [ref=e12]:
|
|
7
|
+
- generic [ref=e13]:
|
|
8
|
+
- generic [ref=e14]: "0"
|
|
9
|
+
- generic [ref=e15]: "1"
|
|
10
|
+
- generic [ref=e16]: Issue
|
|
11
|
+
- button "Collapse issues badge" [ref=e17]:
|
|
12
|
+
- img [ref=e18]
|
|
13
|
+
- alert [ref=e20]
|
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,8 @@ In alpha on the `alpha` dist-tag — install with `npm install fontdue-js@alpha`
|
|
|
7
7
|
- `fontdue-js/preview` — `handlePreviewRequest` (a Web-standard enter/exit route handler), `readPreviewToken`, `previewAuthHeaders`, and the cookie/endpoint constants.
|
|
8
8
|
- `fontdue-js/preview/server` — `runWithPreview`, which holds the preview token in `AsyncLocalStorage` for the duration of a request so every server fetch and preload forwards it automatically, and forces preview responses out of shared/CDN caches so an admin's render is never served to the public.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **One server fetch for every framework.** `fontdue-js/server` exports `createFontdueFetch({ url?, headers?, cacheTags? })` — a ready-made server-side GraphQL fetcher with URL resolution, error handling, `FontdueNotFoundError`, and automatic preview-token forwarding. Each input resolves per call from the explicit option, the per-render config (`runWithPreview` or the Next adapter), then the environment, and passing `cacheTags` opts the fetch into Next's data cache + `/api/revalidate` (inert in other runtimes) — so Next and the other frameworks now share the same fetcher rather than Next hand-rolling its own. See the README "Server-side GraphQL fetches".
|
|
10
|
+
Next.js uses draft mode rather than ambient context, and needs no per-render setup call at all: mounting `<FontdueProvider>` wires every server fetch and the embedded components' server preloads to forward the token, apply the cache tags for `/api/revalidate`, and serve a live render while previewing — including embeds rendered inside a Server Component, which preload server-side with the admin token instead of falling back to a client refetch. See the README "Admin preview".
|
|
11
|
+
- **One server fetch for every framework.** `fontdue-js/server` exports `createFontdueFetch({ url?, headers?, cacheTags? })` — a ready-made server-side GraphQL fetcher with URL resolution, error handling, `FontdueNotFoundError`, and automatic preview-token forwarding. Each input resolves per call from the explicit option, the per-render config (`runWithPreview` elsewhere, or the config the Next adapter resolves per render once `<FontdueProvider>` is mounted), then the environment, and passing `cacheTags` opts the fetch into Next's data cache + `/api/revalidate` (inert in other runtimes) — so Next and the other frameworks now share the same fetcher rather than Next hand-rolling its own. See the README "Server-side GraphQL fetches".
|
|
12
12
|
- **Server-rendered embeds.** Components now render their full HTML on the server where the framework supports it (v2 hydrated some empty and fetched on the client), via the `load{Component}Query()` preload helpers.
|
|
13
13
|
- **Migrating from v2 (Next.js)** is a small, mechanical upgrade — see the README "Migrating a Next.js site from v2". `useFontStyle` is renamed to `useFont` (the old name still works as an alias).
|
|
14
14
|
|
package/README.md
CHANGED
|
@@ -413,30 +413,32 @@ export { POST } from "fontdue-js/next/revalidate";
|
|
|
413
413
|
|
|
414
414
|
and set the Deploy hook URL in your Fontdue admin (Settings → Website settings) to `https://your-site.example/api/revalidate`. Fontdue calls it whenever your site's content changes, purging everything tagged `graphql` so the next request renders fresh.
|
|
415
415
|
|
|
416
|
-
fontdue-js's own server-side fetches opt into Next's data cache (and the `graphql` tag) automatically — static pages revalidated by the deploy hook is the intended way to run a Fontdue site, not dynamic rendering. Give your own fetches the same treatment;
|
|
416
|
+
fontdue-js's own server-side fetches opt into Next's data cache (and the `graphql` tag) automatically — static pages revalidated by the deploy hook is the intended way to run a Fontdue site, not dynamic rendering. Give your own fetches the same treatment; the setup below shows how.
|
|
417
417
|
|
|
418
418
|
### Your own GraphQL fetches
|
|
419
419
|
|
|
420
|
-
Use the same [`createFontdueFetch`](#server-side-graphql-fetches) as every other framework
|
|
420
|
+
Use the same [`createFontdueFetch`](#server-side-graphql-fetches) as every other framework. Mounting `<FontdueProvider>` in your layout is enough to wire it up — there's no per-render setup call:
|
|
421
421
|
|
|
422
422
|
```ts
|
|
423
|
+
// src/lib/graphql.ts
|
|
423
424
|
import { createFontdueFetch } from "fontdue-js/server";
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return fetchFontdue<Q>(name, query, variables);
|
|
425
|
+
|
|
426
|
+
export const fetchGraphql = createFontdueFetch();
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
// any page / layout / generateMetadata
|
|
431
|
+
import { fetchGraphql } from "@/lib/graphql";
|
|
432
|
+
|
|
433
|
+
export default async function Page() {
|
|
434
|
+
const data = await fetchGraphql<IndexQuery>("Index.graphql");
|
|
435
|
+
// …
|
|
436
436
|
}
|
|
437
437
|
```
|
|
438
438
|
|
|
439
|
-
`
|
|
439
|
+
`createFontdueFetch()` resolves its config per fetch from Next's request context: it points your fetches at your site (`NEXT_PUBLIC_FONTDUE_URL`), applies the cache tags that tie them into `/api/revalidate`, and — when a logged-in admin is previewing — forwards the admin token and serves the render live (see [Admin preview](#admin-preview)). Resolving per fetch means soft navigations that re-render only the page segment are covered too, with nothing to repeat per entry point.
|
|
440
|
+
|
|
441
|
+
Route handlers (robots/sitemap) run outside a React render, but the same `createFontdueFetch()` still resolves your site from `NEXT_PUBLIC_FONTDUE_URL`, so they need no extra setup. Their fetches aren't added to Next's data cache (there's no per-render config to tag them), which is what you want for robots/sitemap — they're cheap and rarely change.
|
|
440
442
|
|
|
441
443
|
## Migrating a Next.js site from v2
|
|
442
444
|
|
|
@@ -468,7 +470,7 @@ For a site built on the [example repo](https://github.com/fontdue/example-next)
|
|
|
468
470
|
|
|
469
471
|
Keep the Deploy hook URL in your Fontdue admin pointed at it.
|
|
470
472
|
|
|
471
|
-
4. **Delete caching workarounds you no longer need.** `export const fetchCache = "default-cache"` in the layout (if you added it) is obsolete — fontdue-js opts its own fetches into the data cache now. For your app's own GraphQL fetches, move the transport to `createFontdueFetch
|
|
473
|
+
4. **Delete caching workarounds you no longer need.** `export const fetchCache = "default-cache"` in the layout (if you added it) is obsolete — fontdue-js opts its own fetches into the data cache now. For your app's own GraphQL fetches, move the transport to `createFontdueFetch`; mounting `<FontdueProvider>` then handles caching, `/api/revalidate`, and admin preview for you — see [Your own GraphQL fetches](#your-own-graphql-fetches).
|
|
472
474
|
|
|
473
475
|
5. **Remove the `url` prop from `<FontdueProvider>` if you passed one.** It never configured server-side fetches (server components have no context); v3 resolves everything from `NEXT_PUBLIC_FONTDUE_URL`. The prop still works as a client-side runtime override, but with the env var set you don't need it.
|
|
474
476
|
|
|
@@ -493,10 +495,10 @@ export const fetchGraphql = createFontdueFetch();
|
|
|
493
495
|
const data = await fetchGraphql<IndexQuery>("Index", indexQuery, { slug });
|
|
494
496
|
```
|
|
495
497
|
|
|
496
|
-
`createFontdueFetch({ url?, headers?, cacheTags? })` returns `fetchGraphql(operationName, query, variables?)`. It POSTs to `/graphql`, unwraps `data`, throws on GraphQL errors, and throws `FontdueNotFoundError` when the host doesn't resolve to a site — catch it to render your framework's 404. It's the same fetcher in every framework; each input resolves per call
|
|
498
|
+
`createFontdueFetch({ url?, headers?, cacheTags? })` returns `fetchGraphql(operationName, query, variables?)`. It POSTs to `/graphql`, unwraps `data`, throws on GraphQL errors, and throws `FontdueNotFoundError` when the host doesn't resolve to a site — catch it to render your framework's 404. It's the same fetcher in every framework; each input resolves per call:
|
|
497
499
|
|
|
498
|
-
- **url** —
|
|
499
|
-
- **headers** —
|
|
500
|
+
- **url** — the explicit option, else `FONTDUE_URL` / `PUBLIC_FONTDUE_URL` / `VITE_FONTDUE_URL` from the environment.
|
|
501
|
+
- **headers** — the explicit option merged over the ambient [admin preview](#admin-preview) context (`runWithPreview`), so the preview token is forwarded automatically.
|
|
500
502
|
- **cacheTags** — when present, the fetch opts into Next's data cache (`force-cache` + tags) so `/api/revalidate` can purge it; absent/empty leaves it uncached. The Next hints are inert in other runtimes, where HTML is cached at the response/CDN layer instead.
|
|
501
503
|
|
|
502
504
|
**Upgrading a hand-rolled fetch.** If you already have something like this:
|
|
@@ -525,7 +527,7 @@ export const fetchGraphql = createFontdueFetch();
|
|
|
525
527
|
|
|
526
528
|
You get URL resolution, error handling, `FontdueNotFoundError`, and automatic preview-token forwarding for free.
|
|
527
529
|
|
|
528
|
-
> **Next.js:** the same `createFontdueFetch` —
|
|
530
|
+
> **Next.js:** the same `createFontdueFetch` — mounting `<FontdueProvider>` ties it into Next's data cache, the `/api/revalidate` deploy hook, and admin preview, with no per-render setup call. See [Your own GraphQL fetches](#your-own-graphql-fetches) under the Next adapter.
|
|
529
531
|
|
|
530
532
|
## Admin preview
|
|
531
533
|
|
|
@@ -580,7 +582,7 @@ const preload = await loadTypeTesterQuery(vars, { headers });
|
|
|
580
582
|
|
|
581
583
|
### Next.js
|
|
582
584
|
|
|
583
|
-
Next uses [draft mode](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode) rather than ambient context. The route layers `draftMode()` on top of `handlePreviewRequest`:
|
|
585
|
+
Next uses [draft mode](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode) rather than ambient context. The preview route layers `draftMode()` on top of `handlePreviewRequest`:
|
|
584
586
|
|
|
585
587
|
```ts
|
|
586
588
|
// app/api/preview/route.ts
|
|
@@ -600,36 +602,9 @@ export async function DELETE(request: Request) {
|
|
|
600
602
|
}
|
|
601
603
|
```
|
|
602
604
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
- **`prepareFontdueRender`** — the call you already make at the top of every page folds the draft-mode token into the per-render config, so the embedded components (type testers, store) reveal hidden fonts server-side automatically. This is the Next counterpart to `runWithPreview`.
|
|
606
|
-
- **Your own fetch** reads draft mode and hands the token to `createFontdueFetch`, dropping the cache tags so the render stays live:
|
|
607
|
-
|
|
608
|
-
```ts
|
|
609
|
-
import { draftMode, cookies } from "next/headers";
|
|
610
|
-
import { createFontdueFetch } from "fontdue-js/server";
|
|
611
|
-
import { currentFontdueEndpoint } from "fontdue-js/next";
|
|
612
|
-
import { PREVIEW_TOKEN_COOKIE, previewAuthHeaders } from "fontdue-js/preview";
|
|
613
|
-
|
|
614
|
-
export async function fetchGraphql<Q>(name: string, query: string, variables?: unknown) {
|
|
615
|
-
const endpoint = currentFontdueEndpoint();
|
|
616
|
-
// draftMode()/cookies() are request-scoped — guard build-time static
|
|
617
|
-
// generation, where they throw, as "not preview".
|
|
618
|
-
const isPreview = (await draftMode()).isEnabled;
|
|
619
|
-
const token = isPreview ? (await cookies()).get(PREVIEW_TOKEN_COOKIE)?.value : undefined;
|
|
620
|
-
|
|
621
|
-
const fetchFontdue = createFontdueFetch({
|
|
622
|
-
url: endpoint.origin,
|
|
623
|
-
headers: { ...endpoint.headers, ...previewAuthHeaders(token) },
|
|
624
|
-
// Public renders are tagged + cached; preview renders pass no tags so they
|
|
625
|
-
// stay live and reveal hidden fonts.
|
|
626
|
-
cacheTags: isPreview ? [] : endpoint.tags,
|
|
627
|
-
});
|
|
628
|
-
return fetchFontdue<Q>(name, query, variables);
|
|
629
|
-
}
|
|
630
|
-
```
|
|
605
|
+
That's the only preview-specific code. Mounting `<FontdueProvider>` registers a resolver that reads draft mode and the token cookie per server fetch, so the whole render forwards the token — **your own fetches and the embedded components' server preloads** alike — and hidden fonts show up everywhere, served live. No per-render call is needed (see [Your own GraphQL fetches](#your-own-graphql-fetches)).
|
|
631
606
|
|
|
632
|
-
The example repos wire this up end to end for each framework.
|
|
607
|
+
This includes the Fontdue React components. Rendered in a Server Component (the App Router default), `<TypeTester>`, `<CharacterViewer>` and friends preload their data on the server through that same resolver — even though you never call a `load*Query` helper yourself — so they reveal *their* hidden fonts too. The only embeds that don't depend on it are ones that fetch in the browser: a Fontdue component under a `"use client"` boundary, or the `<fontdue-*>` web components. Those reveal hidden fonts directly via the logged-in admin's session, so they need nothing either way. The example repos wire this up end to end for each framework.
|
|
633
608
|
|
|
634
609
|
## UI config
|
|
635
610
|
|
|
@@ -219,4 +219,37 @@ describe('createFontdueFetch', () => {
|
|
|
219
219
|
expect(init.next).toBeUndefined();
|
|
220
220
|
});
|
|
221
221
|
});
|
|
222
|
+
describe('fontdue-preview header', () => {
|
|
223
|
+
it('sends "false" by default so a public/session-only request never reveals hidden fonts', async () => {
|
|
224
|
+
const fetchMock = mockFetch(() => ({
|
|
225
|
+
status: 200,
|
|
226
|
+
json: async () => ({
|
|
227
|
+
data: {}
|
|
228
|
+
})
|
|
229
|
+
}));
|
|
230
|
+
const fetchGraphql = createFontdueFetch({
|
|
231
|
+
url: 'https://acme.fontdue.com'
|
|
232
|
+
});
|
|
233
|
+
await fetchGraphql('Q', 'query Q { __typename }');
|
|
234
|
+
const init = fetchMock.mock.calls[0][1];
|
|
235
|
+
expect(init.headers['fontdue-preview']).toBe('false');
|
|
236
|
+
});
|
|
237
|
+
it('sends "true" when bound with a preview Bearer token (reveal hidden fonts)', async () => {
|
|
238
|
+
const fetchMock = mockFetch(() => ({
|
|
239
|
+
status: 200,
|
|
240
|
+
json: async () => ({
|
|
241
|
+
data: {}
|
|
242
|
+
})
|
|
243
|
+
}));
|
|
244
|
+
const fetchGraphql = createFontdueFetch({
|
|
245
|
+
url: 'https://acme.fontdue.com',
|
|
246
|
+
headers: {
|
|
247
|
+
authorization: 'Bearer admin-tok'
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
await fetchGraphql('Q', 'query Q { __typename }');
|
|
251
|
+
const init = fetchMock.mock.calls[0][1];
|
|
252
|
+
expect(init.headers['fontdue-preview']).toBe('true');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
222
255
|
});
|
|
@@ -42,10 +42,11 @@ describe('createNetworkFetch (server)', () => {
|
|
|
42
42
|
vi.stubGlobal('fetch', fetchMock);
|
|
43
43
|
|
|
44
44
|
// React.cache doesn't memoize outside a React render, so the store is
|
|
45
|
-
// mocked rather than set through setFontdueServerConfig.
|
|
45
|
+
// mocked rather than set through setFontdueServerConfig. The network layer
|
|
46
|
+
// reads it through resolveFontdueServerConfig (awaited per fetch).
|
|
46
47
|
vi.doMock('../relay/serverConfig', async importActual => ({
|
|
47
48
|
...(await importActual()),
|
|
48
|
-
|
|
49
|
+
resolveFontdueServerConfig: async () => ({
|
|
49
50
|
url: 'http://app:4000',
|
|
50
51
|
headers: {
|
|
51
52
|
'x-forwarded-host': 'acme.fontdue.com'
|
|
@@ -85,4 +86,82 @@ describe('createNetworkFetch (server)', () => {
|
|
|
85
86
|
const [, options] = fetchMock.mock.calls[0];
|
|
86
87
|
expect(options.headers.authorization).toBe('Bearer preview-tok');
|
|
87
88
|
});
|
|
89
|
+
});
|
|
90
|
+
function headersOf(fetchMock) {
|
|
91
|
+
return fetchMock.mock.calls[0][1].headers;
|
|
92
|
+
}
|
|
93
|
+
describe('createNetworkFetch (fontdue-preview header)', () => {
|
|
94
|
+
it('sends fontdue-preview: false on a public server fetch (no token)', async () => {
|
|
95
|
+
vi.stubEnv('FONTDUE_URL', 'https://acme.fontdue.com');
|
|
96
|
+
const fetchMock = vi.fn(async () => ({
|
|
97
|
+
json: async () => ({
|
|
98
|
+
data: {}
|
|
99
|
+
})
|
|
100
|
+
}));
|
|
101
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
102
|
+
const {
|
|
103
|
+
createNetworkFetch
|
|
104
|
+
} = await import("../relay/environment.js");
|
|
105
|
+
await createNetworkFetch()(request, {});
|
|
106
|
+
expect(headersOf(fetchMock)['fontdue-preview']).toBe('false');
|
|
107
|
+
});
|
|
108
|
+
it('sends fontdue-preview: true when a preview Bearer token is forwarded (server)', async () => {
|
|
109
|
+
vi.stubEnv('FONTDUE_URL', 'https://acme.fontdue.com');
|
|
110
|
+
const fetchMock = vi.fn(async () => ({
|
|
111
|
+
json: async () => ({
|
|
112
|
+
data: {}
|
|
113
|
+
})
|
|
114
|
+
}));
|
|
115
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
116
|
+
const {
|
|
117
|
+
createNetworkFetch
|
|
118
|
+
} = await import("../relay/environment.js");
|
|
119
|
+
await createNetworkFetch({
|
|
120
|
+
headers: {
|
|
121
|
+
authorization: 'Bearer tok'
|
|
122
|
+
}
|
|
123
|
+
})(request, {});
|
|
124
|
+
expect(headersOf(fetchMock)['fontdue-preview']).toBe('true');
|
|
125
|
+
});
|
|
126
|
+
it('on the client, sends true only when the preview marker cookie is set', async () => {
|
|
127
|
+
// typeof window must be defined at module load for IS_SERVER to be false.
|
|
128
|
+
vi.stubGlobal('window', {
|
|
129
|
+
addEventListener: () => {}
|
|
130
|
+
});
|
|
131
|
+
vi.stubGlobal('document', {
|
|
132
|
+
cookie: 'fontdue_preview=1'
|
|
133
|
+
});
|
|
134
|
+
vi.stubEnv('NEXT_PUBLIC_FONTDUE_URL', 'https://acme.fontdue.com');
|
|
135
|
+
const fetchMock = vi.fn(async () => ({
|
|
136
|
+
json: async () => ({
|
|
137
|
+
data: {}
|
|
138
|
+
})
|
|
139
|
+
}));
|
|
140
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
141
|
+
const {
|
|
142
|
+
createNetworkFetch
|
|
143
|
+
} = await import("../relay/environment.js");
|
|
144
|
+
await createNetworkFetch()(request, {});
|
|
145
|
+
expect(headersOf(fetchMock)['fontdue-preview']).toBe('true');
|
|
146
|
+
});
|
|
147
|
+
it('on the client, sends false when the marker cookie is absent (logged-in admin browsing normally)', async () => {
|
|
148
|
+
vi.stubGlobal('window', {
|
|
149
|
+
addEventListener: () => {}
|
|
150
|
+
});
|
|
151
|
+
vi.stubGlobal('document', {
|
|
152
|
+
cookie: 'other=1'
|
|
153
|
+
});
|
|
154
|
+
vi.stubEnv('NEXT_PUBLIC_FONTDUE_URL', 'https://acme.fontdue.com');
|
|
155
|
+
const fetchMock = vi.fn(async () => ({
|
|
156
|
+
json: async () => ({
|
|
157
|
+
data: {}
|
|
158
|
+
})
|
|
159
|
+
}));
|
|
160
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
161
|
+
const {
|
|
162
|
+
createNetworkFetch
|
|
163
|
+
} = await import("../relay/environment.js");
|
|
164
|
+
await createNetworkFetch()(request, {});
|
|
165
|
+
expect(headersOf(fetchMock)['fontdue-preview']).toBe('false');
|
|
166
|
+
});
|
|
88
167
|
});
|