@weaverse/next 0.1.0-alpha.3 → 0.1.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,165 +1,448 @@
1
1
  # @weaverse/next
2
2
 
3
- Next.js App Router integration for building Weaverse-powered storefronts.
3
+ Next.js App Router integration for Weaverse-powered Hydrogen storefronts.
4
4
 
5
- This package adapts the Weaverse runtime (component registry, page-tree
6
- rendering, Studio bridge) to Next.js without depending on React Router or
7
- Hydrogen. It mirrors the `@weaverse/hydrogen` contract where it makes sense, but
8
- takes plain, framework-neutral inputs (URL, headers, search params) instead of a
9
- React Router `Request`/`LoaderFunctionArgs`.
5
+ This package lets a Next App Router storefront render Weaverse page data, run server-side section loaders, connect to Weaverse Studio, and refresh loader-backed Studio edits without depending on React Router.
6
+
7
+ It is the Next equivalent of the useful parts of `@weaverse/hydrogen`, but the app creates the request-scoped server client explicitly because Next does not inject a custom `context` object into every route loader.
8
+
9
+ ## Current status
10
+
11
+ - Package status: alpha (`0.1.0-alpha.x`).
12
+ - Target runtime: Next App Router.
13
+ - Peer dependencies: `next >=14`, `react >=19`, `react-dom >=19`.
14
+ - Studio bridge: supported through the existing Studio bridge script plus Next-native runtime callbacks.
15
+ - Resource-picker revalidation: supported through per-item loader revalidation, not full-page `router.refresh()` on the happy path.
16
+
17
+ ## Quick answers for the team
18
+
19
+ ### 1. What is this package?
20
+
21
+ `@weaverse/next` is the Weaverse adapter for Next App Router storefronts. It provides:
22
+
23
+ - a server client that fetches Weaverse page/theme data from the Weaverse public API;
24
+ - a component registry and renderer for Weaverse page trees;
25
+ - hooks and provider equivalents for theme settings, page data, commerce context, and Weaverse runtime access;
26
+ - Studio script connection and runtime binding;
27
+ - a per-item loader revalidation route handler for resource-picker/live-preview edits.
28
+
29
+ ### 2. Are `loadPage` / `loadThemeSettings` implemented or only typed?
30
+
31
+ They are implemented in `@weaverse/next/server`.
32
+
33
+ Use `createWeaverseNextServerClient()` to create a request-scoped client, then call:
34
+
35
+ - `weaverse.loadPage({ type: 'INDEX' | 'PRODUCT' | 'COLLECTION', handle? })`
36
+ - `weaverse.loadThemeSettings()`
37
+
38
+ Implementation lives in [`src/server/server-client.ts`](./src/server/server-client.ts). It:
39
+
40
+ - resolves `projectId` from Studio query params, config, or env;
41
+ - POSTs page requests to `/api/public/project`;
42
+ - POSTs theme settings requests to `/api/public/project_configs`;
43
+ - runs registered component loaders and attaches `loaderData`;
44
+ - forces `no-store` for design/revision preview reads;
45
+ - returns safe client-facing configs including `requestInfo`.
46
+
47
+ ### 3. Is the POC bootstrapped from Shopify's Hydrogen preview docs?
48
+
49
+ Yes. The POC started as a Next App Router app plus Shopify Hydrogen preview setup:
50
+
51
+ ```bash
52
+ npx create-next-app@latest weaverse-hydrogen-next-poc --ts --app --eslint --tailwind --no-src-dir --import-alias "@/*" --use-npm --yes
53
+ npx @shopify/hydrogen@preview setup
54
+ ```
55
+
56
+ See the POC findings doc:
57
+
58
+ - https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/findings.md
59
+
60
+ ### 4. Where does the POC load page + Weaverse data?
61
+
62
+ Reference POC files:
63
+
64
+ - Server client helper: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/weaverse-next/server.ts
65
+ - Home route load: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/page.tsx
66
+ - Product route load: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/products/%5Bhandle%5D/page.tsx
67
+ - Collection route load: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/collections/%5Bhandle%5D/page.tsx
68
+ - Client wrapper/renderer: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/weaverse-next/wrapper.tsx
69
+ - Studio script connector: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/weaverse-next/studio-connect.tsx
70
+ - Per-item revalidation route: https://github.com/Weaverse/weaverse-hydrogen-next-poc/blob/main/app/api/weaverse/revalidate/route.ts
71
+
72
+ The route pattern is:
73
+
74
+ ```tsx
75
+ // app/page.tsx
76
+ import { loadWeaverseHomePage } from './weaverse-next/server'
77
+ import { WeaverseHome } from './weaverse-next/wrapper'
78
+
79
+ export default async function Home(props: {
80
+ searchParams: Promise<Record<string, string | string[] | undefined>>
81
+ }) {
82
+ let weaverseData = await loadWeaverseHomePage(props.searchParams)
83
+ return <WeaverseHome data={weaverseData} />
84
+ }
85
+ ```
86
+
87
+ ### 5. Is there a global app load context like Hydrogen/Pilot?
88
+
89
+ Not automatically from the framework.
90
+
91
+ Hydrogen/React Router injects `context.weaverse` into route loaders. Next App Router does not have that loader-context mechanism. The Next equivalent is a small app-owned server helper that creates the request-scoped Weaverse server client at the route boundary.
92
+
93
+ In the POC that helper is `getWeaverseServerClient(...)` in `app/weaverse-next/server.ts`. It plays the same role as Hydrogen's global load context, but explicitly:
94
+
95
+ ```ts
96
+ let weaverse = await getWeaverseServerClient(searchParams, pathname)
97
+ let page = await weaverse.loadPage({ type: 'INDEX' })
98
+ ```
99
+
100
+ So the mental model is still `weaverse.loadPage(...)`; only the injection mechanism changes.
10
101
 
11
102
  ## Entrypoints
12
103
 
13
104
  | Import | Use in | Purpose |
14
105
  | --- | --- | --- |
15
- | `@weaverse/next` | Client & server | Registry, renderer, provider, hooks, Studio runtime/bridge. |
16
- | `@weaverse/next/server` | Server only | Server-side Weaverse client that fetches page/theme data from the Weaverse API. |
106
+ | `@weaverse/next` | Client and shared code | Component registry, provider, renderer, hooks, Studio runtime/bind components, schema re-exports. |
107
+ | `@weaverse/next/server` | Server only | Real Weaverse API client, config resolution, page URL normalization, per-item revalidation route handler. |
17
108
 
18
- > The server-side data-loading API lives under **`@weaverse/next/server`** so the
19
- > root entry stays free of server-only fetch assumptions and safe to import from
20
- > client components.
109
+ The server API lives under `@weaverse/next/server` so client components can safely import the root entry without pulling server-only fetch code into the client bundle.
21
110
 
22
111
  ## Package structure
23
112
 
24
113
  ```text
25
114
  packages/next/src/
26
- index.ts # root (@weaverse/next) public exports
27
- server.ts # server (@weaverse/next/server) public exports
28
- client.ts # createWeaverseNextClient (injected-fetcher client)
29
- loader.ts # runWeaverseComponentLoaders (server-side section loaders)
30
- registry.ts # register components with the Weaverse element registry
31
- renderer.tsx # WeaverseNextRenderer (page-tree → React)
32
- provider.tsx # WeaverseNextProvider (client context for hooks)
33
- hooks.ts # useThemeSettings / useWeaversePageData / ...
34
- runtime.ts # WeaverseNextRuntime + Studio binding
35
- studio-bridge.tsx # Studio script injection / connect
36
- request-info.ts # buildWeaverseNextRequestInfo (Studio-compatible)
37
- theme-settings-store.ts
38
- types.ts # shared types (root + server)
115
+ index.ts # root public exports
116
+ server.ts # server public exports
117
+ client.ts # createWeaverseNextClient for client/runtime state
118
+ loader.ts # runWeaverseComponentLoaders
119
+ registry.ts # component registration for @weaverse/react
120
+ renderer.tsx # WeaverseNextRenderer
121
+ provider.tsx # WeaverseNextProvider
122
+ hooks.ts # Next adapter hooks
123
+ runtime.ts # WeaverseNextRuntime + Studio binding
124
+ studio-connect.tsx # root Studio script loader
125
+ studio-bridge.tsx # page-level Studio runtime binder
126
+ studio-router.ts # Next router -> Studio internal callbacks
127
+ use-weaverse-next-studio.tsx # Next-native Studio binder using next/navigation
128
+ revalidate-item.ts # client per-item loader revalidation
129
+ request-info.ts # Studio-compatible request info
130
+ theme-settings-store.ts # Studio-compatible theme settings store
131
+ types.ts # shared public types
39
132
  server/
40
- server-client.ts # createWeaverseNextServerClient
41
- configs.ts # getWeaverseNextConfigs (host/api-base/flags/env)
42
- normalize-page-url.ts
133
+ server-client.ts # createWeaverseNextServerClient
134
+ revalidate-handler.ts # createWeaverseNextRevalidateHandler
135
+ configs.ts # host/api/env/design-mode config resolution
136
+ normalize-page-url.ts # stable page URL normalization
43
137
  ```
44
138
 
45
139
  ## Root API (`@weaverse/next`)
46
140
 
47
- - `createWeaverseNextClient(config)` — request-safe client that delegates network
48
- I/O to injected `fetchPage` / `fetchThemeSettings`.
49
- - `WeaverseNextProvider` / `WeaverseNextRenderer` — client provider + page-tree
50
- renderer.
51
- - `runWeaverseComponentLoaders(args)` — walk a page tree and run each registered
52
- component's `loader` server-side.
53
- - Hooks: `useThemeSettings`, `useWeaversePageData`, `useWeaverseRootData`,
54
- `useWeaverseCommerce` (plus re-exported `@weaverse/react` hooks).
55
- - Studio helpers: `WeaverseNextRuntime`, `createWeaverseNextRuntime`,
56
- `bindWeaverseNextStudioRuntime`, `WeaverseNextStudioBridge`,
57
- `WeaverseNextStudioConnect`, `resolveWeaverseNextStudioScriptSrc`,
58
- `buildWeaverseNextRequestInfo`, `createWeaverseNextThemeSettingsStore`.
59
- - `createSchema` (re-exported from `@weaverse/schema`).
141
+ Main exports:
60
142
 
61
- ## Server API (`@weaverse/next/server`)
143
+ - `createWeaverseNextClient(config)` — client/runtime state holder. It registers client render components and carries request/page/theme context.
144
+ - `WeaverseNextProvider` — React provider for the client runtime.
145
+ - `WeaverseNextRenderer` — renders a Weaverse page tree into React and binds the Studio runtime.
146
+ - `runWeaverseComponentLoaders(args)` — walks a page tree and runs component `loader` functions server-side.
147
+ - `WeaverseNextStudioConnect` — root client component that loads the Next Studio bridge script in design/preview contexts.
148
+ - `WeaverseNextStudio` / `WeaverseNextStudioBridge` — Next-native page-level Studio runtime binding.
149
+ - `createWeaverseNextRuntime` / `bindWeaverseNextStudioRuntime` — lower-level runtime helpers.
150
+ - `revalidateWeaverseNextItem()` — client-side per-item revalidation helper wired by `WeaverseNextStudio`.
151
+ - Hooks: `useThemeSettings`, `useWeaversePageData`, `useWeaverseRootData`, `useWeaverseCommerce`.
152
+ - Re-exported migration helpers from `@weaverse/react`: `useWeaverse`, `useParentInstance`, `useItemInstance`, `useChildInstances`.
153
+ - `createSchema` and schema types from `@weaverse/schema`.
62
154
 
63
- `createWeaverseNextServerClient(config)` returns a client that performs the real
64
- Weaverse public API fetch — the equivalent of Hydrogen/Pilot's
65
- `context.weaverse.loadPage(...)`.
155
+ ## Server API (`@weaverse/next/server`)
66
156
 
67
- ### Next App Router server context model
157
+ Main exports:
68
158
 
69
- Hydrogen's classic storefront runtime is built on React Router. Its request
70
- handler accepts `getLoadContext(request)`, creates a Hydrogen context, and React
71
- Router injects that object into every route loader. That is why a Pilot route can
72
- call `context.weaverse.loadPage(...)` directly.
159
+ - `createWeaverseNextServerClient(config)` — request-scoped server client.
160
+ - `createWeaverseNextRevalidateHandler(config)` — route-handler factory for Studio per-item loader revalidation.
161
+ - `getWeaverseNextConfigs(...)` — config/env/query resolution.
162
+ - `normalizeNextPageUrl(...)` / `resolveRequestUrl(...)` — stable page URL helpers.
73
163
 
74
- Next App Router does not inject an app-defined global loader context into
75
- `app/page.tsx`. A page is a Server Component whose framework-provided inputs are
76
- `params` and `searchParams`; request-scoped data is read with Next functions such
77
- as `headers()` / `cookies()`. Hydrogen preview follows that framework model for
78
- Next examples: fetch in `app/page.tsx` or a server helper, then pass the snapshot
79
- to client components.
164
+ ### `createWeaverseNextServerClient`
80
165
 
81
- So the Next equivalent is explicit server-client creation at the route boundary:
166
+ Example helper for a Next route/server component boundary:
82
167
 
83
168
  ```ts
84
169
  import { createWeaverseNextServerClient } from '@weaverse/next/server'
85
- import { components } from '@/weaverse/components'
170
+ import { headers } from 'next/headers'
171
+ import { serverComponents } from './server-components'
172
+ import { getStaticStorefrontClient } from './storefront'
86
173
 
87
- export async function loadWeaverse(request: Request, storefront: Storefront) {
88
- let url = new URL(request.url)
89
- let weaverse = createWeaverseNextServerClient({
90
- projectId: process.env.WEAVERSE_PROJECT_ID, // string | (ctx) => string | Promise<string>
91
- components,
92
- themeSchema,
93
- commerce: { storefront },
174
+ export async function getWeaverseServerClient(
175
+ searchParamsPromise: Promise<Record<string, string | string[] | undefined>>,
176
+ pathname = '/'
177
+ ) {
178
+ let [headersList, rawSearchParams] = await Promise.all([
179
+ headers(),
180
+ searchParamsPromise,
181
+ ])
182
+ let requestHeaders = new Headers(Object.fromEntries(headersList.entries()))
183
+ let searchParams = new URLSearchParams()
184
+
185
+ for (let [key, value] of Object.entries(rawSearchParams)) {
186
+ if (Array.isArray(value)) {
187
+ for (let item of value) searchParams.append(key, item)
188
+ } else if (value !== undefined) {
189
+ searchParams.set(key, value)
190
+ }
191
+ }
192
+
193
+ let url = new URL(
194
+ `${pathname}?${searchParams}`,
195
+ `${requestHeaders.get('x-forwarded-proto') ?? 'https'}://${
196
+ requestHeaders.get('x-forwarded-host') ?? requestHeaders.get('host')
197
+ }`
198
+ )
199
+
200
+ let storefront = getStaticStorefrontClient()
201
+
202
+ return createWeaverseNextServerClient({
203
+ components: serverComponents,
204
+ projectId: process.env.WEAVERSE_PROJECT_ID,
205
+ weaverseHost: process.env.WEAVERSE_HOST,
94
206
  env: process.env,
207
+ commerce: storefront
208
+ ? {
209
+ storefront: {
210
+ i18n: { country: 'US', language: 'EN' },
211
+ query: async (query, options) => {
212
+ let response = await storefront.graphql(query, options as never)
213
+ return response.data
214
+ },
215
+ },
216
+ }
217
+ : undefined,
95
218
  requestContext: {
96
219
  url,
97
- headers: request.headers,
98
- searchParams: url.searchParams,
99
- i18n: storefront.i18n,
220
+ headers: requestHeaders,
221
+ searchParams,
222
+ pathname,
223
+ i18n: { country: 'US', language: 'EN', locale: 'en-US' },
100
224
  },
101
225
  cache: { revalidate: 60, tags: ['weaverse'] },
102
226
  })
103
-
104
- let [page, theme] = await Promise.all([
105
- weaverse.loadPage({ type: 'INDEX' }),
106
- weaverse.loadThemeSettings(),
107
- ])
108
- return { page, theme, weaverse }
109
227
  }
110
228
  ```
111
229
 
112
- ### Returned client
230
+ Then each route can stay close to the Hydrogen mental model:
231
+
232
+ ```ts
233
+ let weaverse = await getWeaverseServerClient(props.searchParams, '/')
234
+ let data = await weaverse.loadPage({ type: 'INDEX' })
235
+ let theme = await weaverse.loadThemeSettings()
236
+ ```
237
+
238
+ ### Server client members
113
239
 
114
240
  | Member | Description |
115
241
  | --- | --- |
116
- | `loadPage(input?)` | POSTs `{ projectId, url, i18n, params, isDesignMode }` to `/api/public/project`, runs component loaders, returns `WeaverseNextLoaderData` (`page`, `project`, `pageAssignment`, `configs.requestInfo`). In Studio preview mode (`?isPreviewMode=true`) it short-circuits to a synthetic single-section page built from the `sectionType` presets — no fetch, no `projectId` required. A route-level `projectId` override is propagated to `client.projectId`, loaders, and the returned `configs`. |
117
- | `loadThemeSettings(options?)` | POSTs to `/api/public/project_configs`, merges `themeSchema` defaults under `theme`, includes `staticContent`; falls back to defaults on failure. |
118
- | `fetchWithCache<T>(url, options?)` | Mode-aware fetch: `cache: 'no-store'` in design/revision, `next: { revalidate, tags }` in published mode. |
119
- | `projectId` / `resolveProjectId()` | Resolved project id (query → function → string → env). |
120
- | `components`, `commerce`, `storefront`, `requestContext`, `themeSchema`, `themeSettings`, `data`, `dataContext`, `configs` | Readonly request state. `storefront` aliases `commerce.storefront`. |
121
-
122
- ### Config resolution
123
-
124
- Close to Hydrogen's `getWeaverseConfigs`:
125
-
126
- - **projectId** — `?weaverseProjectId=` query → config function → config string →
127
- `WEAVERSE_PROJECT_ID` env.
128
- - **weaverseHost** — trusted request `?weaverseHost=` (only `weaverse.io` /
129
- `weaverse.dev` over https) → `WEAVERSE_HOST` env → `https://studio.weaverse.io`.
130
- - **API base** — trusted request host → `WEAVERSE_PUBLIC_API_BASE` env → custom
131
- non-production `WEAVERSE_HOST` → `https://api.weaverse.io`.
132
- - **publicEnv** — `PUBLIC_STORE_DOMAIN`, `PUBLIC_STOREFRONT_API_TOKEN`.
133
-
134
- `WEAVERSE_API_KEY` is read from the request query / env and kept on the internal
135
- base configs, but the current server client does **not** attach it to the
136
- page/theme API requests. It is also **never** serialized into client-facing
137
- `configs` / loader data, so it cannot leak to the browser.
138
-
139
- ## Studio helpers
140
-
141
- Design/preview detection, the Studio bridge script, and the in-page runtime are
142
- shared with the root entry: `createWeaverseNextRuntime` + `WeaverseNextProvider`
143
- hydrate Studio, and `bindWeaverseNextStudioRuntime` wires live updates. The
144
- server client emits `configs.requestInfo` and design-mode flags the bridge
145
- expects.
146
-
147
- > **Mount `WeaverseNextStudioConnect` at your root/client shell.**
148
- > `WeaverseNextRenderer` mounts the in-page runtime bridge but does **not**
149
- > inject the Studio bridge script by itself. The script (which lets Studio's
150
- > `checkWeaversePage()` handshake answer on every route, including content-less
151
- > ones) is loaded by `WeaverseNextStudioConnect`, so it must be rendered once at
152
- > the app root/client shell for the Studio editor to connect.
242
+ | `loadPage(input?)` | Fetches the page from `/api/public/project`, builds `WeaverseNextLoaderData`, runs component loaders, and returns page/project/config data. In section preview mode it can synthesize a single-section preview page. |
243
+ | `loadThemeSettings(options?)` | Fetches `/api/public/project_configs`, merges schema defaults under merchant settings, returns theme settings/static content/schema data for design mode. |
244
+ | `fetchWithCache<T>(url, options?)` | Next-aware fetch helper. Uses `cache: 'no-store'` in design/revision preview and `next: { revalidate, tags }` in published mode. |
245
+ | `resolveProjectId()` / `projectId` | Resolves project id from Studio query, config function/string, or env. |
246
+ | `components` | Server component registry. Components may include `schema` and optional `loader`. |
247
+ | `commerce` / `storefront` | Commerce context. `storefront` is a compatibility alias for Pilot-style loaders. |
248
+ | `requestContext` | URL, headers, search params, i18n, and design/preview flags. |
249
+
250
+ ### Component loaders
251
+
252
+ A registered component may expose a server-side `loader`:
253
+
254
+ ```ts
255
+ export let productHero = {
256
+ schema: productHeroSchema,
257
+ loader: async ({ data, commerce, weaverse }) => {
258
+ let handle = data.product?.handle
259
+ if (!handle) return null
260
+
261
+ return commerce?.storefront?.query(PRODUCT_QUERY, {
262
+ variables: { handle },
263
+ })
264
+ },
265
+ }
266
+ ```
267
+
268
+ Loader args match the page-load and revalidation paths:
269
+
270
+ - `data` — schema defaults merged under item data.
271
+ - `commerce` — explicit commerce context.
272
+ - `weaverse` — server/client object with `weaverse.storefront` compatibility alias.
273
+ - `context` — request context.
274
+
275
+ ## Rendering in a Next route
276
+
277
+ Typical shape:
278
+
279
+ ```tsx
280
+ // app/page.tsx
281
+ export default async function Home(props: {
282
+ searchParams: Promise<Record<string, string | string[] | undefined>>
283
+ }) {
284
+ let data = await loadWeaverseHomePage(props.searchParams)
285
+ return <WeaversePage data={data} />
286
+ }
287
+ ```
288
+
289
+ ```tsx
290
+ // app/weaverse-page.tsx
291
+ 'use client'
292
+
293
+ import {
294
+ createWeaverseNextClient,
295
+ WeaverseNextProvider,
296
+ WeaverseNextRenderer,
297
+ } from '@weaverse/next'
298
+ import type { WeaverseNextLoaderData } from '@weaverse/next'
299
+ import { components } from './components'
300
+
301
+ export function WeaversePage({ data }: { data: WeaverseNextLoaderData }) {
302
+ let requestInfo = data.configs?.requestInfo as
303
+ | {
304
+ i18n?: { country?: string; language?: string; locale?: string }
305
+ pathname?: string
306
+ search?: string
307
+ }
308
+ | undefined
309
+ let projectId =
310
+ (data.configs?.projectId as string | undefined) ?? data.project?.id
311
+ if (!projectId) throw new Error('Missing Weaverse project id')
312
+
313
+ let client = createWeaverseNextClient({
314
+ projectId,
315
+ components,
316
+ requestContext: {
317
+ pathname: requestInfo?.pathname ?? '/',
318
+ searchParams: new URLSearchParams(requestInfo?.search ?? ''),
319
+ isDesignMode: Boolean(data.configs?.isDesignMode),
320
+ isPreviewMode: Boolean(data.configs?.isPreviewMode),
321
+ isRevisionPreview: Boolean(data.configs?.isRevisionPreview),
322
+ i18n: requestInfo?.i18n,
323
+ },
324
+ })
325
+
326
+ return (
327
+ <WeaverseNextProvider client={client}>
328
+ <WeaverseNextRenderer data={data} />
329
+ </WeaverseNextProvider>
330
+ )
331
+ }
332
+ ```
333
+
334
+ ## Studio setup
335
+
336
+ Studio has two separate pieces.
337
+
338
+ ### 1. Root-level script connector
339
+
340
+ Mount `WeaverseNextStudioConnect` once near the app root. It loads the Next Studio bridge script in design/preview/revision contexts and does nothing in published mode.
341
+
342
+ ```tsx
343
+ // app/weaverse-next/studio-connect.tsx
344
+ 'use client'
345
+
346
+ import { WeaverseNextStudioConnect } from '@weaverse/next'
347
+
348
+ export function StudioConnect() {
349
+ return <WeaverseNextStudioConnect />
350
+ }
351
+ ```
352
+
353
+ ```tsx
354
+ // app/layout.tsx
355
+ import { StudioConnect } from './weaverse-next/studio-connect'
356
+
357
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
358
+ return (
359
+ <html lang="en">
360
+ <body>
361
+ <StudioConnect />
362
+ {children}
363
+ </body>
364
+ </html>
365
+ )
366
+ }
367
+ ```
368
+
369
+ ### 2. Page-level runtime binding
370
+
371
+ `WeaverseNextRenderer` creates and binds the page runtime. Internally, `WeaverseNextStudio` maps Studio internals to Next App Router:
372
+
373
+ - `internal.navigate(...)` → `router.push(...)` / `router.replace(...)`
374
+ - `internal.revalidate()` → `router.refresh()` fallback
375
+ - `internal.revalidateItem(draftItem)` → per-item loader revalidation endpoint
376
+
377
+ The root script connector and page-level renderer are both required for full Studio editing.
378
+
379
+ ## Per-item Studio revalidation
380
+
381
+ Resource-picker edits should refresh only the affected item's server loader data. The happy path should not call `router.refresh()` because refreshing the whole RSC tree can remount the page and reset scroll.
382
+
383
+ Mount the route handler in the consuming app:
384
+
385
+ ```ts
386
+ // app/api/weaverse/revalidate/route.ts
387
+ import { createWeaverseNextRevalidateHandler } from '@weaverse/next/server'
388
+ import { getWeaverseServerClient } from '../../../weaverse-next/server'
389
+
390
+ export const { POST } = createWeaverseNextRevalidateHandler({
391
+ getClient: () => getWeaverseServerClient(Promise.resolve({})),
392
+ })
393
+ ```
394
+
395
+ Flow:
396
+
397
+ ```text
398
+ Studio resource picker edit
399
+ -> Builder calls runtime.internal.revalidateItem(draftItem)
400
+ -> SDK POSTs { draftItem } to /api/weaverse/revalidate
401
+ -> route handler finds the registered component by draftItem.type
402
+ -> handler runs that component's loader with draft item data
403
+ -> response returns { loaderData }
404
+ -> SDK applies loaderData to the live item instance in place
405
+ ```
406
+
407
+ If the route is missing or returns an error, Builder falls back to the older route-refresh path.
408
+
409
+ ## Config resolution
410
+
411
+ Server config resolution intentionally mirrors Hydrogen where possible:
412
+
413
+ - `projectId`: `?weaverseProjectId=` → config function → config string → `WEAVERSE_PROJECT_ID`.
414
+ - `weaverseHost`: trusted `?weaverseHost=` over `https://*.weaverse.io` / `https://*.weaverse.dev` → `WEAVERSE_HOST` → `https://studio.weaverse.io`.
415
+ - API base: trusted request host → `WEAVERSE_PUBLIC_API_BASE` → non-production `WEAVERSE_HOST` → `https://api.weaverse.io`.
416
+ - public env: `PUBLIC_STORE_DOMAIN`, `PUBLIC_STOREFRONT_API_TOKEN`.
417
+
418
+ `WEAVERSE_API_KEY` may be read into internal base configs but is not attached to page/theme API requests and is never serialized into client-facing loader data.
419
+
420
+ ## POC reference
421
+
422
+ Live POC:
423
+
424
+ - https://weaverse-hydrogen-next-poc.vercel.app
425
+
426
+ Repo:
427
+
428
+ - https://github.com/Weaverse/weaverse-hydrogen-next-poc
429
+
430
+ Important POC paths:
431
+
432
+ ```text
433
+ app/weaverse-next/server.ts # explicit Next server context helper
434
+ app/weaverse-next/wrapper.tsx # client provider + renderer
435
+ app/weaverse-next/studio-connect.tsx # root Studio script connector
436
+ app/api/weaverse/revalidate/route.ts # per-item loader revalidation route
437
+ app/page.tsx # home page Weaverse load
438
+ app/products/[handle]/page.tsx # product route Weaverse load
439
+ app/collections/[handle]/page.tsx # collection route Weaverse load
440
+ ```
153
441
 
154
442
  ## Current limitations
155
443
 
156
- - Server fetch has a timeout + single attempt; it does not yet replicate
157
- Hydrogen's subrequest cache, retry/backoff, or merchant-overrides translation
158
- fetch.
159
- - Next caching is expressed only via `cache: 'no-store'` and
160
- `next: { revalidate, tags }`; on-demand `revalidatePath`/`revalidateTag` is left
161
- to the host app.
162
- - `loadCustomPages` (sitemap) and SEO helpers are not ported yet.
163
- - The Studio bridge currently reuses the Hydrogen bundle until a Next-specific
164
- bundle is required.
165
- - Package is pre-release (`alpha`); the API may still change.
444
+ - This package is still alpha; API names may change before stable.
445
+ - Next does not provide Hydrogen's global React Router loader context, so apps should keep a small `getWeaverseServerClient(...)` helper.
446
+ - Full Shopify request-handler/redirect parity is app-level follow-up work, not owned by this package yet.
447
+ - Translation/static-text sidecar parity, multiple Weaverse runtimes on one URL, and 404/error-route Studio connection are later hardening items.
448
+ - The Studio bridge currently reuses the existing Studio bridge bundle until a Next-specific bundle is proven necessary.