@storystick/next 0.0.0-stage → 0.2.0-rc.1
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/LICENSE +112 -0
- package/PUBLIC-API.md +31 -0
- package/README.md +522 -2
- package/dist/.source-hash +1 -0
- package/dist/cache-tags.d.ts +2 -0
- package/dist/cache-tags.js +12 -0
- package/dist/clean.d.ts +1 -0
- package/dist/clean.js +1 -0
- package/dist/code-version.d.ts +29 -0
- package/dist/code-version.js +48 -0
- package/dist/file.d.ts +1 -0
- package/dist/file.js +1 -0
- package/dist/i18n-seo.d.ts +1 -0
- package/dist/i18n-seo.js +1 -0
- package/dist/image.d.ts +11 -0
- package/dist/image.js +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.react-server.d.ts +3 -0
- package/dist/index.react-server.js +12 -0
- package/dist/link.d.ts +10 -0
- package/dist/link.js +8 -0
- package/dist/preview/index.d.ts +29 -0
- package/dist/preview/index.js +24 -0
- package/dist/preview/internal.d.ts +61 -0
- package/dist/preview/internal.js +97 -0
- package/dist/preview/preview-request-identity.d.ts +4 -0
- package/dist/preview/preview-request-identity.js +63 -0
- package/dist/preview/provenance-config.d.ts +42 -0
- package/dist/preview/provenance-config.js +132 -0
- package/dist/preview/provenance-frame-builder.d.ts +6 -0
- package/dist/preview/provenance-frame-builder.js +10 -0
- package/dist/preview/provenance-response.d.ts +5 -0
- package/dist/preview/provenance-response.js +9 -0
- package/dist/preview/provenance-runtime-adapter.d.ts +9 -0
- package/dist/preview/provenance-runtime-adapter.js +42 -0
- package/dist/preview/render-frame-provenance.d.ts +23 -0
- package/dist/preview/render-frame-provenance.js +47 -0
- package/dist/preview/route-authoring.d.ts +3 -0
- package/dist/preview/route-authoring.js +80 -0
- package/dist/preview/route-context.d.ts +3 -0
- package/dist/preview/route-context.js +38 -0
- package/dist/preview/route-disable.d.ts +7 -0
- package/dist/preview/route-disable.js +63 -0
- package/dist/preview/route-document-provenance.d.ts +23 -0
- package/dist/preview/route-document-provenance.js +46 -0
- package/dist/preview/route-errors.d.ts +1 -0
- package/dist/preview/route-errors.js +1 -0
- package/dist/preview/route-handler.d.ts +36 -0
- package/dist/preview/route-handler.js +139 -0
- package/dist/preview/route-initial.d.ts +9 -0
- package/dist/preview/route-initial.js +156 -0
- package/dist/preview/route-provenance-runtime.d.ts +34 -0
- package/dist/preview/route-provenance-runtime.js +90 -0
- package/dist/preview/route-token.d.ts +6 -0
- package/dist/preview/route-token.js +46 -0
- package/dist/preview/server-preview-internals.d.ts +3 -0
- package/dist/preview/server-preview-internals.js +3 -0
- package/dist/preview/slice-locale.d.ts +5 -0
- package/dist/preview/slice-locale.js +15 -0
- package/dist/preview-error-surface.d.ts +7 -0
- package/dist/preview-error-surface.js +20 -0
- package/dist/preview-exit.d.ts +7 -0
- package/dist/preview-exit.js +71 -0
- package/dist/preview-metadata.d.ts +15 -0
- package/dist/preview-metadata.js +13 -0
- package/dist/preview-mode-latch.d.ts +133 -0
- package/dist/preview-mode-latch.js +231 -0
- package/dist/preview-nav-location.d.ts +12 -0
- package/dist/preview-nav-location.js +149 -0
- package/dist/preview-nav.d.ts +94 -0
- package/dist/preview-nav.js +523 -0
- package/dist/preview-refresh-coordinator.d.ts +26 -0
- package/dist/preview-refresh-coordinator.js +93 -0
- package/dist/preview-render-error.d.ts +11 -0
- package/dist/preview-render-error.js +36 -0
- package/dist/preview-render-recovery.d.ts +9 -0
- package/dist/preview-render-recovery.js +42 -0
- package/dist/preview-session-recovery.d.ts +1 -0
- package/dist/preview-session-recovery.js +1 -0
- package/dist/preview-standalone-host.d.ts +3 -0
- package/dist/preview-standalone-host.js +55 -0
- package/dist/preview-text.d.ts +2 -0
- package/dist/preview-text.js +1 -0
- package/dist/preview-view-bootstrap.d.ts +5 -0
- package/dist/preview-view-bootstrap.js +13 -0
- package/dist/public-delivery.d.ts +6 -0
- package/dist/public-delivery.js +7 -0
- package/dist/query-fetch-options.d.ts +7 -0
- package/dist/query-fetch-options.js +193 -0
- package/dist/query.d.ts +2 -0
- package/dist/query.js +1 -0
- package/dist/rendering.d.ts +33 -0
- package/dist/rendering.js +42 -0
- package/dist/revalidate.d.ts +12 -0
- package/dist/revalidate.js +33 -0
- package/dist/rich-text.d.ts +2 -0
- package/dist/rich-text.js +1 -0
- package/dist/server-runtime.d.ts +208 -0
- package/dist/server-runtime.js +1584 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +6 -0
- package/dist/slice-zone.d.ts +2 -0
- package/dist/slice-zone.js +1 -0
- package/dist/storystick-component-types.d.ts +26 -0
- package/dist/storystick-component-types.js +1 -0
- package/dist/storystick-component.d.ts +10 -0
- package/dist/storystick-component.js +45 -0
- package/dist/storystick-error-recovery.d.ts +12 -0
- package/dist/storystick-error-recovery.js +11 -0
- package/dist/storystick-preview-runtime.d.ts +30 -0
- package/dist/storystick-preview-runtime.js +561 -0
- package/dist/storystick.d.ts +24 -0
- package/dist/storystick.js +23 -0
- package/dist/testing.d.ts +9 -0
- package/dist/testing.js +40 -0
- package/dist/video.d.ts +1 -0
- package/dist/video.js +1 -0
- package/package.json +128 -3
- package/recipes/README.md +91 -0
- package/recipes/resource-mirror.ts +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-MIT
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Tanguy Freycon
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the MIT license that is effective on the second anniversary of the date we make
|
|
91
|
+
the Software available. On or after that date, you may use the Software under
|
|
92
|
+
the MIT license, in which case the following will apply:
|
|
93
|
+
|
|
94
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
95
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
96
|
+
the Software without restriction, including without limitation the rights to
|
|
97
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
98
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
99
|
+
so, subject to the following conditions:
|
|
100
|
+
|
|
101
|
+
The above copyright notice and this permission notice shall be included in all
|
|
102
|
+
copies or substantial portions of the Software.
|
|
103
|
+
|
|
104
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
105
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
106
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
107
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
108
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
109
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
110
|
+
SOFTWARE.
|
|
111
|
+
|
|
112
|
+
This Agreement shall be governed by the laws of France.
|
package/PUBLIC-API.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @storystick/next API contract
|
|
2
|
+
|
|
3
|
+
Install this candidate explicitly with `@storystick/next@0.2.0-rc.1` after registry bootstrap and publication. This repository alone does not make the candidate or beta channel available on npm.
|
|
4
|
+
|
|
5
|
+
Version 0.2.0-rc.1, cohort `sdk-0.2.0-rc.1`. ESM distribution; no CommonJS or universal Edge compatibility is promised.
|
|
6
|
+
|
|
7
|
+
This package is a product integration facade. Reserved contracts are required between SDK packages and must be upgraded with the same cohort. Their public export paths do not promise independent stability.
|
|
8
|
+
|
|
9
|
+
| Entry | Status |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `.` | Supported beta entry |
|
|
12
|
+
| `./preview-text` | Supported beta entry |
|
|
13
|
+
| `./image` | Supported beta entry |
|
|
14
|
+
| `./file` | Supported beta entry |
|
|
15
|
+
| `./video` | Supported beta entry |
|
|
16
|
+
| `./link` | Supported beta entry |
|
|
17
|
+
| `./query` | Supported beta entry |
|
|
18
|
+
| `./rich-text` | Supported beta entry |
|
|
19
|
+
| `./slice-zone` | Supported beta entry |
|
|
20
|
+
| `./revalidate` | Supported beta entry |
|
|
21
|
+
| `./preview-nav` | Reserved SDK contract |
|
|
22
|
+
| `./preview-nav-location` | Reserved SDK contract |
|
|
23
|
+
| `./preview` | Supported beta entry |
|
|
24
|
+
| `./testing` | Reserved SDK contract |
|
|
25
|
+
| `./server` | Supported beta entry |
|
|
26
|
+
|
|
27
|
+
The qualified toolchain is Node 24.19.0 and pnpm 9.15.9. Next uses App Router 15.5.28, Sharp 0.35.5 and React/React DOM 19.2.8; Astro uses 7.4.4 with the Node adapter 11.1.9. Browser delivery is verified by bundling; hosted Preview activation and cross-browser behavior require the staging manual check. Source/CLI are Node tools, server entries belong on the server, and browser entries must never receive a server API key.
|
|
28
|
+
|
|
29
|
+
Use a generated Query V2 definition. A missing document/singleton returns null, an empty collection returns an empty array, and fetch throws on blocked/degraded execution. Execute exposes the complete result envelope. RSC metadata must remain plain JSON data; request authority must not be stored in shared module state.
|
|
30
|
+
|
|
31
|
+
The repository is private. Public package availability does not make the monorepo public, and npm provenance from that repository is unavailable. Product and pilot support: [Storystick](https://storystick.app).
|
package/README.md
CHANGED
|
@@ -1,3 +1,523 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @storystick/next
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Minimal content reads and Preview for the Next.js 15 App Router, with React 19.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @storystick/next@beta
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Configure once
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// lib/site.ts
|
|
13
|
+
import { createSite } from '@storystick/next/server'
|
|
14
|
+
import * as definition from '@/generated/storystick/query'
|
|
15
|
+
|
|
16
|
+
export const site = createSite({
|
|
17
|
+
definition,
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Generate the definition with the Storystick CLI. Credentials come from the
|
|
22
|
+
project connection environment variables. Routable Story types are inferred
|
|
23
|
+
from the generated definition, the site URL comes from
|
|
24
|
+
`STORYSTICK_SITE_URL`, and the project primary locale is available through
|
|
25
|
+
`await site.locale()`. Override these only when an application deliberately
|
|
26
|
+
uses a different policy. The site object contains immutable configuration; it
|
|
27
|
+
never stores a current user's session.
|
|
28
|
+
|
|
29
|
+
## Read where you render
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { previewTextProps } from '@storystick/next'
|
|
33
|
+
import { site } from '@/lib/site'
|
|
34
|
+
|
|
35
|
+
export async function Header() {
|
|
36
|
+
const client = await site.client()
|
|
37
|
+
const settings = await client.query('siteSettings').selectAll().fetch()
|
|
38
|
+
return settings && <header {...previewTextProps(settings.fields.siteName)} />
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This example assumes a `siteSettings` singleton with a `siteName` field. Keep
|
|
43
|
+
this component in a native layout, page, group or slot. It does not depend on
|
|
44
|
+
a CMS page initializing context. Put `<Storystick />` from `@storystick/next`
|
|
45
|
+
once in a shared layout. Export `GET` and `POST` from `site.handlers` in
|
|
46
|
+
`app/api/storystick/[...action]/route.ts`.
|
|
47
|
+
|
|
48
|
+
In detached Preview, the SDK owns the editing panel outside the site's layout
|
|
49
|
+
tree. Replacing a layout in the same client navigation preserves the panel and
|
|
50
|
+
its editing connection. Removing `<Storystick />` entirely, leaving Preview, or
|
|
51
|
+
changing project/view ends that lifetime. A full page reload creates a new
|
|
52
|
+
connection; an editor-initiated navigation can restore the panel at its confirmed
|
|
53
|
+
destination. Editor navigation waits for saving and context acceptance, while
|
|
54
|
+
page overlays wait for the corresponding site render. No additional provider or
|
|
55
|
+
SDK option is needed.
|
|
56
|
+
|
|
57
|
+
For a controlled schema migration, give each candidate deployment a distinct
|
|
58
|
+
`STORYSTICK_APP_BUILD_ID=app:<build-id>` and set
|
|
59
|
+
`STORYSTICK_APP_BUILD_DIGEST` to the build artifact's lowercase SHA-256. The
|
|
60
|
+
opt-in `/api/storystick/migration-readback` endpoint then reports this build's
|
|
61
|
+
project, generated query-schema fingerprint and the schema version actually
|
|
62
|
+
read through its server SDK. The ordinary health route stays lightweight.
|
|
63
|
+
These values are deployment claims plus an observed endpoint read, not a
|
|
64
|
+
cryptographic attestation of every running instance or a replacement for
|
|
65
|
+
checking B in Studio Preview before authority transfer.
|
|
66
|
+
|
|
67
|
+
Queries use Query V2 directly: `select`, `where`, `orderBy`, `page` and `by`.
|
|
68
|
+
`.fetch()` returns exact data and throws `QueryExecutionError` for blocked or
|
|
69
|
+
degraded execution. Missing singletons/documents return `null`; empty pages
|
|
70
|
+
return `[]`. `.execute()` returns the result envelope, including pagination
|
|
71
|
+
and diagnostics, when an application needs those details.
|
|
72
|
+
|
|
73
|
+
For a selection shared by several components, use the generated identity helper
|
|
74
|
+
instead of recreating a query abstraction:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { defineSelection } from '@/generated/storystick/query'
|
|
78
|
+
|
|
79
|
+
export const postCard = defineSelection('blogPost', {
|
|
80
|
+
title: true,
|
|
81
|
+
slug: true,
|
|
82
|
+
coverImage: true,
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`client.query('blogPost').select(postCard)` keeps the exact selected result
|
|
87
|
+
type. The helper emits no query syntax and adds no runtime work.
|
|
88
|
+
|
|
89
|
+
Configured server reads carry public edit addresses with their values in authorized
|
|
90
|
+
Preview. Ordinary async components, including later JSX descendants, need no
|
|
91
|
+
render wrapper or query registration. A global projected-generation subscription
|
|
92
|
+
refreshes the native Next tree, including queries that previously returned null
|
|
93
|
+
or an empty array. Changes are grouped in a fixed 50ms window; another refresh
|
|
94
|
+
waits for the current React transition. Stale projections do not trigger reads.
|
|
95
|
+
This updates more of the server tree than mounted-query targeting.
|
|
96
|
+
|
|
97
|
+
Keep stable React keys and native layout placement to retain client state.
|
|
98
|
+
Preview strings contain invisible provenance: use a cleaned copy for string
|
|
99
|
+
comparisons, lengths and other computation, and `previewTextProps` for native
|
|
100
|
+
text elements (see below). Derived values have no guessed edit target. Images,
|
|
101
|
+
links, rich text and slices retain the existing renderer helpers; custom
|
|
102
|
+
non-text surfaces can use explicit edit attributes.
|
|
103
|
+
|
|
104
|
+
## Optional CMS pages
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { notFound, redirect, permanentRedirect } from 'next/navigation'
|
|
108
|
+
import { site } from '@/lib/site'
|
|
109
|
+
import { PageContent } from '@/components/page-content'
|
|
110
|
+
|
|
111
|
+
type Props = {
|
|
112
|
+
params: Promise<{ pathname?: string[] }>
|
|
113
|
+
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default async function Page(props: Props) {
|
|
117
|
+
const route = await site.resolvePage(props)
|
|
118
|
+
if (route.type === 'redirect') {
|
|
119
|
+
const navigate = route.permanent ? permanentRedirect : redirect
|
|
120
|
+
navigate(route.redirectTo)
|
|
121
|
+
}
|
|
122
|
+
if (route.type !== 'document') notFound()
|
|
123
|
+
return <PageContent document={route.document} />
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function generateMetadata(props: Props) {
|
|
127
|
+
const route = await site.resolvePage(props)
|
|
128
|
+
return site.metadata(route, route.type === 'document'
|
|
129
|
+
? { title: route.document.fields.title } : {})
|
|
130
|
+
}
|
|
131
|
+
export const generateStaticParams = site.params
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`PageContent` is application code. `resolvePage` accepts native catch-all props
|
|
135
|
+
or explicit `{ pathname, searchParams }`. Native-props redirects retain query
|
|
136
|
+
parameters. Public pages do not consume dynamic search parameters unless needed
|
|
137
|
+
for a redirect. `site.metadata` cleans metadata and sets localized alternates,
|
|
138
|
+
canonical URLs and Preview noindex. Sitemaps use `site.sitemapIds()` and
|
|
139
|
+
`site.sitemap()`. Use native `error.tsx` for render failures. If an error boundary
|
|
140
|
+
can replace the layout containing `<Storystick />`, mount
|
|
141
|
+
`<StorystickErrorRecovery error={error} reset={reset} />` beside its fallback UI:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
'use client'
|
|
145
|
+
import { StorystickErrorRecovery } from '@storystick/next'
|
|
146
|
+
|
|
147
|
+
export default function ErrorPage({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
148
|
+
return <main role="alert">
|
|
149
|
+
<StorystickErrorRecovery error={error} reset={reset} />
|
|
150
|
+
<p>This page could not be displayed.</p>
|
|
151
|
+
<button onClick={reset}>Try again</button>
|
|
152
|
+
</main>
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This keeps Studio navigation available after a render failure, including moves
|
|
157
|
+
between a route-less draft and the homepage. Passing `error` preserves the
|
|
158
|
+
distinction between transient content-service failures (which Studio can recover) and customer rendering bugs (which are not
|
|
159
|
+
automatically retried), including in production where Next redacts error details.
|
|
160
|
+
Ordinary public visits do not load the Preview runtime. Startup failures that prevent the error component itself from
|
|
161
|
+
mounting still require a full reload after the underlying problem is fixed.
|
|
162
|
+
|
|
163
|
+
A native route needs neither a CMS document nor a catch-all. It keeps its own
|
|
164
|
+
metadata responsibility; `storystickPreviewRobots({ enabled })` is available
|
|
165
|
+
when handling metadata outside the CMS route helper. `site.page`,
|
|
166
|
+
`site.component`, `StorystickDocument` and `StorystickEditable` are removed.
|
|
167
|
+
|
|
168
|
+
See the [published language routes and resource mirror recipes](recipes/README.md)
|
|
169
|
+
for `site.alternates()`, translated paths, `publicOrigins` / `domainLocale`, and an
|
|
170
|
+
explicit French interface with an English fallback publication.
|
|
171
|
+
|
|
172
|
+
## Layouts, locales and Preview
|
|
173
|
+
|
|
174
|
+
`site.client()` uses the project's primary locale; `site.client({ locale })`
|
|
175
|
+
selects another explicitly. Slice reads inherit their source document locale.
|
|
176
|
+
For multilingual `<html lang>`, put the layout at a native segment that owns
|
|
177
|
+
the language parameter. A root layout does not receive descendant parameters.
|
|
178
|
+
Headers and footers can remain in their layouts and retain local client state.
|
|
179
|
+
They remain editable on native routes without CMS page documents. Selecting
|
|
180
|
+
a field changes the Studio editor target while retaining the actual view.
|
|
181
|
+
|
|
182
|
+
The standard Preview integration shares one authorized reading perspective
|
|
183
|
+
per browser/project/origin. Each view retains its own navigation and edit
|
|
184
|
+
target. Variant/release selection changes the shared perspective; individual
|
|
185
|
+
URLs are not its authority. No middleware, proxy, generated webpack alias,
|
|
186
|
+
customer-link rewrite or provenance flag is required.
|
|
187
|
+
|
|
188
|
+
Routing, read observations and JSON metadata live in framework-neutral
|
|
189
|
+
packages. This package supplies the Next request and router adapter; it does
|
|
190
|
+
not imply that Astro, Vue or Svelte adapters have been released.
|
|
191
|
+
|
|
192
|
+
The root entry exposes the ordinary rendering and client surfaces:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import {
|
|
196
|
+
previewTextProps,
|
|
197
|
+
RichText,
|
|
198
|
+
SliceZone,
|
|
199
|
+
StorystickImage,
|
|
200
|
+
StorystickLink,
|
|
201
|
+
} from '@storystick/next'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Direct subpaths remain available as explicit bundle boundaries, but the starter
|
|
205
|
+
and ordinary application code need only `@storystick/next`.
|
|
206
|
+
|
|
207
|
+
## Render text fields
|
|
208
|
+
|
|
209
|
+
SDK reads return native strings, including their provenance during authorized
|
|
210
|
+
Preview. Move that metadata out of the text node when rendering a field:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import { previewTextProps } from '@storystick/next'
|
|
214
|
+
|
|
215
|
+
<h1 {...previewTextProps(fields.title)} />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`previewTextProps` returns plain `children` and a `data-ss-p` attribute for one
|
|
219
|
+
complete field. It adds no wrapper, provider or client lifecycle, and its props
|
|
220
|
+
can cross a Server Component boundary. The runtime validates the provenance
|
|
221
|
+
before making the existing element an edit target. Plain production values
|
|
222
|
+
produce only `children`.
|
|
223
|
+
|
|
224
|
+
In client components that only need this helper, use
|
|
225
|
+
`import { previewTextProps } from '@storystick/next/preview-text'` to keep the
|
|
226
|
+
import limited to the pure text adapter. The root export remains available
|
|
227
|
+
alongside the SDK's other rendering primitives.
|
|
228
|
+
|
|
229
|
+
Use the original field before formatting or concatenation. A composite value
|
|
230
|
+
is cleaned without assigning the element to a guessed field. Render fields
|
|
231
|
+
on separate elements for separate targets; for transformed text, clean the
|
|
232
|
+
display value and use explicit field bindings when an edit target is needed.
|
|
233
|
+
Rendering a provenance-bearing string directly in JSX does not guarantee
|
|
234
|
+
visually identical text across browsers and typography settings.
|
|
235
|
+
|
|
236
|
+
## Clean values before computation and attributes
|
|
237
|
+
|
|
238
|
+
String comparison is the first case to protect because a failed comparison
|
|
239
|
+
leaves no detectable browser signal. Compare a cleaned copy, and pass the
|
|
240
|
+
original field to `previewTextProps` to retain its edit target:
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
import { previewTextProps, stripPreviewProvenance } from '@storystick/next'
|
|
244
|
+
|
|
245
|
+
function ComparisonCell({ value }: { value: string }) {
|
|
246
|
+
const comparable = stripPreviewProvenance(value)
|
|
247
|
+
if (comparable === '✓') return <CheckIcon />
|
|
248
|
+
return <span {...previewTextProps(value)} />
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Use `stripPreviewProvenance(value)` before comparisons, custom attributes,
|
|
253
|
+
`placeholder`, `aria-*`, custom metadata, JSON-LD, or another structured
|
|
254
|
+
payload. The helper deep-copies JSON-shaped input, removes preview coded-text
|
|
255
|
+
payloads and the reserved `_ssMarker` / `__ssPreviewProvenance` keys, and never
|
|
256
|
+
mutates the source. It is client-compatible from `@storystick/next` and is also
|
|
257
|
+
re-exported by `@storystick/next/server` for server-only code.
|
|
258
|
+
|
|
259
|
+
Cleaning a field before passing it to `previewTextProps` removes its edit
|
|
260
|
+
target. Keep the original value for that helper and clean separate copies for
|
|
261
|
+
computation and other sinks.
|
|
262
|
+
|
|
263
|
+
## Render image fields
|
|
264
|
+
|
|
265
|
+
The zero-config component reads the request-owned asset sidecar registered by
|
|
266
|
+
`resolvePage`, so an ordinary render only passes the field value:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import { StorystickImage } from '@storystick/next'
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<StorystickImage
|
|
273
|
+
image={fields.image}
|
|
274
|
+
loading="eager"
|
|
275
|
+
fetchPriority="high"
|
|
276
|
+
/>
|
|
277
|
+
)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Images are lazy and responsive by default, using a bounded `srcset` and
|
|
281
|
+
`sizes="auto, 100vw"`. Eager images use `100vw`; set `responsive={false}` for
|
|
282
|
+
a single source or pass `options.sizes` when the layout has a more precise
|
|
283
|
+
contract. An explicit `source` remains authoritative for rendering outside a bound
|
|
284
|
+
request: `options={{ source: assets }}`. Transformations, `widths` and `sizes`
|
|
285
|
+
all live in this single options object. An explicit top-level `alt` overrides
|
|
286
|
+
the field; `alt=""` remains decorative.
|
|
287
|
+
|
|
288
|
+
A valid version LQIP is consumed automatically and removed after the final
|
|
289
|
+
image decodes. Use `placeholder="none"` to opt out. Without an LQIP the
|
|
290
|
+
component remains a server-rendered native `<img>` with no client lifecycle.
|
|
291
|
+
When `sizes` is present without `widths`, Storystick emits its bounded standard
|
|
292
|
+
responsive scale capped to the intrinsic asset width.
|
|
293
|
+
|
|
294
|
+
Native events and React 19 refs are supported inside your Client Components,
|
|
295
|
+
including when the image uses an LQIP. Functions still cannot cross a Server
|
|
296
|
+
Component boundary: create handlers in a Client Component, or pass the plain
|
|
297
|
+
JSON from `resolveImage().props` to your own client renderer.
|
|
298
|
+
|
|
299
|
+
In development a non-renderable image logs a warning and leaves an inspectable
|
|
300
|
+
`[data-storystick-image-status]` element at its render position. In production
|
|
301
|
+
it renders `null` by default. Pass `fallback` for app-owned degradation in both
|
|
302
|
+
environments; a render function receives the discriminated resolver failure:
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
<StorystickImage
|
|
306
|
+
image={fields.coverImage}
|
|
307
|
+
fallback={({ status }) => <CoverPlaceholder reason={status} />}
|
|
308
|
+
/>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`getNextImageProps()` remains available for explicit `next/image` interop. It
|
|
312
|
+
is not the default path: Storystick already serves canonical provider-native
|
|
313
|
+
transform URLs and avoids remote-pattern configuration and double optimization.
|
|
314
|
+
|
|
315
|
+
### Optional `next/image` loader
|
|
316
|
+
|
|
317
|
+
Storystick's default `<img>` path already serves provider-native canonical
|
|
318
|
+
URLs. If an app needs a `next/image` loader, keep it pure: rewrite only the
|
|
319
|
+
last transform-key segment, round the requested width up through the grammar,
|
|
320
|
+
and map Next's numeric quality onto the three finite cache tiers.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import type { ImageLoaderProps } from 'next/image'
|
|
324
|
+
import {
|
|
325
|
+
buildImageTransformKey,
|
|
326
|
+
parseImageTransformKey,
|
|
327
|
+
roundUpImageDimensionBucket,
|
|
328
|
+
type CanonicalImageTransform,
|
|
329
|
+
type ImageTransformQuality,
|
|
330
|
+
} from '@storystick/asset-grammar'
|
|
331
|
+
|
|
332
|
+
function qualityTier(quality = 75): ImageTransformQuality {
|
|
333
|
+
if (quality <= 55) return 'low'
|
|
334
|
+
if (quality <= 77) return 'medium'
|
|
335
|
+
return 'high'
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function storystickImageLoader({
|
|
339
|
+
src,
|
|
340
|
+
width,
|
|
341
|
+
quality,
|
|
342
|
+
}: ImageLoaderProps): string {
|
|
343
|
+
const bucket = roundUpImageDimensionBucket(width)
|
|
344
|
+
if (bucket === null) throw new Error('Unsupported Storystick image width')
|
|
345
|
+
|
|
346
|
+
const url = new URL(src)
|
|
347
|
+
const path = url.pathname.split('/')
|
|
348
|
+
const encodedKey = path.pop()
|
|
349
|
+
if (!encodedKey) throw new Error('Missing Storystick transform key')
|
|
350
|
+
|
|
351
|
+
const parsed = parseImageTransformKey(decodeURIComponent(encodedKey))
|
|
352
|
+
const transform: CanonicalImageTransform =
|
|
353
|
+
parsed.kind === 'transform'
|
|
354
|
+
? parsed.transform
|
|
355
|
+
: { schemaVersion: 1 }
|
|
356
|
+
const nextKey = buildImageTransformKey({
|
|
357
|
+
...transform,
|
|
358
|
+
resize: {
|
|
359
|
+
width: bucket,
|
|
360
|
+
fit: transform.resize?.fit ?? 'scale-down',
|
|
361
|
+
},
|
|
362
|
+
quality: qualityTier(quality),
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
path.push(encodeURIComponent(nextKey))
|
|
366
|
+
url.pathname = path.join('/')
|
|
367
|
+
return url.toString()
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Install `@storystick/asset-grammar` directly when using this recipe. Bucket
|
|
372
|
+
rounding fails closed above the largest supported width instead of creating an
|
|
373
|
+
unbounded transformation key.
|
|
374
|
+
|
|
375
|
+
## Advanced server integration
|
|
376
|
+
|
|
377
|
+
`createServerClient` and `resolvePage` remain available from
|
|
378
|
+
`@storystick/next/server`. They require
|
|
379
|
+
explicit schema/site information that `createSite` binds once. Prefer direct
|
|
380
|
+
Query V2 reads to route-wide data declarations when slices have different
|
|
381
|
+
filters or selections.
|
|
382
|
+
|
|
383
|
+
There is no `ss` namespace. Use `site.params()`, `site.sitemap()` and
|
|
384
|
+
`site.sitemapIds()` for the configured site. Read singletons with
|
|
385
|
+
`(await site.client()).query('settings').selectAll().fetch()`. Custom static
|
|
386
|
+
parameters use paginated Query V2 selections and your own mapping, rather
|
|
387
|
+
than a predefined `slug` field or segment name.
|
|
388
|
+
|
|
389
|
+
Locale information is explicit: `route.localeContext` comes from
|
|
390
|
+
`site.resolvePage()`, and `site.client({ locale })` configures an independent
|
|
391
|
+
reader. The pure `prefixHref(href, locale, settings)` helper from
|
|
392
|
+
`prefixHref`, re-exported by `@storystick/next`, builds localized links. It does not
|
|
393
|
+
require another component to resolve a page first. Native layouts can pass
|
|
394
|
+
this information through ordinary props or application context.
|
|
395
|
+
|
|
396
|
+
`resolvePreviewDraft({ sessionId, locale? })` and
|
|
397
|
+
`isPreviewDraftActive(sessionId)` are advanced draft-target helpers for a
|
|
398
|
+
native page that already owns a view selector. They do not infer the request
|
|
399
|
+
URL from private headers. Layout content does not need either helper.
|
|
400
|
+
|
|
401
|
+
## HTTP routes and deployment
|
|
402
|
+
|
|
403
|
+
`site.handlers` wraps `createRouteHandler` from `@storystick/next/preview`.
|
|
404
|
+
It handles activation, access renewal, view context commands, exit, health,
|
|
405
|
+
Preview runtime delivery and signed revalidation. `createSite({ routes })`
|
|
406
|
+
accepts the same handler options; direct handler construction remains possible.
|
|
407
|
+
|
|
408
|
+
Activation verifies the origin and capability, enables Draft Mode, and stores a
|
|
409
|
+
signed HttpOnly admission for the project and customer origin. This admission
|
|
410
|
+
**authorizes access; it does not select a perspective**. Each opening from Studio
|
|
411
|
+
creates a navigation view attached to the authenticated project's current
|
|
412
|
+
perspective. The URL's `_ss_session` identifies the view for navigation and
|
|
413
|
+
selection; ordinary links do not need to carry it. Reopening or returning to
|
|
414
|
+
Studio observes the shared perspective without replaying an old launch.
|
|
415
|
+
Document write targets are still derived from the selected perspective, document,
|
|
416
|
+
locale and release membership.
|
|
417
|
+
|
|
418
|
+
### Navigation and perspectives
|
|
419
|
+
|
|
420
|
+
Use ordinary `next/link` or `<a>` links, and `StorystickLink` for CMS links.
|
|
421
|
+
The existing `<Storystick />` mount and Preview handlers own the integration:
|
|
422
|
+
no Preview navigation provider, middleware, session prop or per-link helper is required.
|
|
423
|
+
The SDK never rewrites customer DOM.
|
|
424
|
+
|
|
425
|
+
A human authentication session shares one perspective per project and customer
|
|
426
|
+
origin. Studio and detached previews observe the same revision; their current
|
|
427
|
+
page, locale and field selection remain local. Layouts and pages read that
|
|
428
|
+
perspective through the existing admission. Public requests retain static/ISR
|
|
429
|
+
rendering; admitted Preview reads stay outside public caches.
|
|
430
|
+
|
|
431
|
+
Choose **Published** in the perspective selector to read active publications
|
|
432
|
+
while keeping the toolbar. Published uses the current frontend build with
|
|
433
|
+
published content, without draft substitution or optimistic edits. A document or
|
|
434
|
+
language without a publication remains unavailable. Choose Main, a variant or a
|
|
435
|
+
release to resume editing. Changing perspective never publishes content.
|
|
436
|
+
|
|
437
|
+
Perspective commands use compare-and-set revisions and idempotent receipts.
|
|
438
|
+
A conflicting command is reported instead of silently replayed. Other surfaces
|
|
439
|
+
adopt the observed perspective after preserving their pending edits in their
|
|
440
|
+
original scope. Closing a panel does not end the shared context.
|
|
441
|
+
|
|
442
|
+
Production Preview requires HTTPS and an allowed customer origin. Grants are
|
|
443
|
+
addressed by project and normalized browser origin (including port), with the
|
|
444
|
+
configured base path as the cookie path. Reopen existing previews after this
|
|
445
|
+
upgrade so that their grants attach to the shared authority. Next Draft Mode is
|
|
446
|
+
an infrastructure flag, not authorization for private content. Published keeps
|
|
447
|
+
this admission active; it does not call the disable handler. Reopen from Studio
|
|
448
|
+
after deploying a new application build.
|
|
449
|
+
|
|
450
|
+
For simultaneous production Previews of separate Next builds, use distinct
|
|
451
|
+
hostnames. Next's own `__prerender_bypass` cookie is shared by hostname, uses
|
|
452
|
+
`Path=/`, and is bound to a build; another application on a different port or
|
|
453
|
+
base path can replace it. Storystick preserves Next's production static/ISR gate
|
|
454
|
+
and does not rename or emulate this framework cookie. Development and explicit
|
|
455
|
+
local lanes read the addressed Storystick grant directly.
|
|
456
|
+
|
|
457
|
+
For a custom native page outside the CMS route helper, metadata and native error handling
|
|
458
|
+
remain application-owned. For non-DOM transformations of rendered content,
|
|
459
|
+
keep the explicit cleaning boundary described above.
|
|
460
|
+
|
|
461
|
+
Signed publish webhooks use `STORYSTICK_WEBHOOK_SECRET`. Keep that value and
|
|
462
|
+
`STORYSTICK_API_KEY` server-only. Reverse proxies must preserve the customer
|
|
463
|
+
origin through trusted forwarded host/protocol headers. No proxy is required
|
|
464
|
+
by the SDK itself, and server action origin settings are not a replacement
|
|
465
|
+
for configuring the proxy correctly.
|
|
466
|
+
|
|
467
|
+
Licensed under [FSL-1.1-MIT](./LICENSE).
|
|
468
|
+
|
|
469
|
+
### Rendering independent content
|
|
470
|
+
|
|
471
|
+
Values returned by `site.client()` and `site.resolvePage()` already own their
|
|
472
|
+
rendering dependencies. Use `<SliceZone slices={document.fields.sections}
|
|
473
|
+
components={components} />` and `<RichText content={author.fields.bio} />` without
|
|
474
|
+
technical context. Optional `context={{ document, parent }}` is application data
|
|
475
|
+
only. Native layouts and async components remain ordinary Next components;
|
|
476
|
+
no document wrapper or additional Provider is needed.
|
|
477
|
+
|
|
478
|
+
Richtext is a JSON object on reads. Reserved content metadata survives JSON/RSC
|
|
479
|
+
transport and must be preserved when passing rendering values. Component
|
|
480
|
+
registries contain functions: import the registry in its rendering environment,
|
|
481
|
+
rather than passing server functions through a client boundary.
|
|
482
|
+
|
|
483
|
+
Async slice functions inherit the locale carried by their content, including
|
|
484
|
+
nested slices and rich-text embeds. Awaited helpers inside that function share
|
|
485
|
+
the same locale. Independent components rendered later use the site's default,
|
|
486
|
+
or `site.client({ locale })`; no layout-global locale is assumed. Application
|
|
487
|
+
`context` is never inspected for SDK locale selection.
|
|
488
|
+
|
|
489
|
+
Automatic cache tags are bounded and share their encoding with invalidation.
|
|
490
|
+
Custom tags retain Next's 256-character limit, with at most 127 distinct custom
|
|
491
|
+
tags so every read can keep its project invalidation tag. Excess automatic tags
|
|
492
|
+
are omitted only when project invalidation still covers the read.
|
|
493
|
+
|
|
494
|
+
Public Query V2 delivery can use `STORYSTICK_DELIVERY_ENDPOINT`. Public framework
|
|
495
|
+
reads now default to `no-store`; explicit Next Data Cache requires a finite
|
|
496
|
+
positive `next.revalidate`. Publication handlers accept the shared v2 ready event,
|
|
497
|
+
and `signWebhookBody` is asynchronous. For renderer-owned receipts, deployment
|
|
498
|
+
manifests, recovery and qualified modes, see the repository's
|
|
499
|
+
[publication contract](../../services/public-content-gateway/PUBLICATION.md).
|
|
500
|
+
|
|
501
|
+
For ordinary static publication, declare `createSite({ definition,
|
|
502
|
+
publicRendering: 'static' })` and supply a new `VERCEL_DEPLOYMENT_ID` or
|
|
503
|
+
`STORYSTICK_DEPLOYMENT_ID` for **every compilation**, including CMS-only rebuilds
|
|
504
|
+
of the same Git commit. Freeze it in the compiled artifact, for example using
|
|
505
|
+
Next's `env` configuration. This mode keeps Next's native build-time caching
|
|
506
|
+
and scopes content, locale and route-inventory fetches to that deployment, so a
|
|
507
|
+
restored `.next/cache` cannot serve a previous build's content. Do not use the Git
|
|
508
|
+
SHA as this identity or reuse an ID for a new compilation. Pages and sitemap
|
|
509
|
+
must be activated together. Preview and explicit fresh reads remain `no-store`;
|
|
510
|
+
do not use `dynamic = 'force-static'` on pages that support Draft Mode. Static
|
|
511
|
+
publication still needs an actual rebuild and rendered proof, and does not
|
|
512
|
+
qualify managed schema migration.
|
|
513
|
+
|
|
514
|
+
The repository checks this against the actual marketing app with
|
|
515
|
+
`node scripts/marketing-build-qualification.mjs --cache-rebuild`: retained fetch
|
|
516
|
+
cache, changed content/locales/routes/sitemap, then a content-origin outage that
|
|
517
|
+
must fail the next build. See the [Next 15 cache behavior](https://nextjs.org/docs/15/app/guides/caching).
|
|
518
|
+
|
|
519
|
+
## Candidate distribution contract
|
|
520
|
+
|
|
521
|
+
See [PUBLIC-API.md](./PUBLIC-API.md) for supported entries, reserved SDK contracts,
|
|
522
|
+
the qualified runtime matrix and the candidate cohort. Upgrade Storystick
|
|
523
|
+
packages together.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20e8248dcde770c1a3966e62bbd395af132defa0ac1ada5c01ce32f486997919
|