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

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
@@ -11,8 +11,9 @@ It is the Next equivalent of the useful parts of `@weaverse/hydrogen`, but the a
11
11
  - Package status: alpha (`0.1.0-alpha.x`).
12
12
  - Target runtime: Next App Router.
13
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.
14
+ - Studio bridge: supported through the dedicated Next Studio bridge script (`/static/studio/next/*`) plus Next-native runtime callbacks.
15
15
  - Resource-picker revalidation: supported through per-item loader revalidation, not full-page `router.refresh()` on the happy path.
16
+ - Low-risk Hydrogen parity helpers: custom-page sitemap fetching, Next metadata conversion, and resource-picker type aliases are available.
16
17
 
17
18
  ## Quick answers for the team
18
19
 
@@ -127,6 +128,7 @@ packages/next/src/
127
128
  use-weaverse-next-studio.tsx # Next-native Studio binder using next/navigation
128
129
  revalidate-item.ts # client per-item loader revalidation
129
130
  request-info.ts # Studio-compatible request info
131
+ seo.ts # Next Metadata-compatible page SEO helpers
130
132
  theme-settings-store.ts # Studio-compatible theme settings store
131
133
  types.ts # shared public types
132
134
  server/
@@ -160,6 +162,7 @@ Main exports:
160
162
  - `createWeaverseNextRevalidateHandler(config)` — route-handler factory for Studio per-item loader revalidation.
161
163
  - `getWeaverseNextConfigs(...)` — config/env/query resolution.
162
164
  - `normalizeNextPageUrl(...)` / `resolveRequestUrl(...)` — stable page URL helpers.
165
+ - `getWeaverseNextSeoMetadata(...)` / `formatWeaverseNextSeoMetadata(...)` — convert Weaverse `page.seo` into a Next Metadata-compatible object.
163
166
 
164
167
  ### `createWeaverseNextServerClient`
165
168
 
@@ -241,6 +244,7 @@ let theme = await weaverse.loadThemeSettings()
241
244
  | --- | --- |
242
245
  | `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
246
  | `loadThemeSettings(options?)` | Fetches `/api/public/project_configs`, merges schema defaults under merchant settings, returns theme settings/static content/schema data for design mode. |
247
+ | `fetchCustomPages(options?)` | Fetches published custom pages from `/api/public/v1/projects/:projectId/custom-pages` for sitemap generation, paginating automatically and returning partial results if a later page fails. |
244
248
  | `fetchWithCache<T>(url, options?)` | Next-aware fetch helper. Uses `cache: 'no-store'` in design/revision preview and `next: { revalidate, tags }` in published mode. |
245
249
  | `resolveProjectId()` / `projectId` | Resolves project id from Studio query, config function/string, or env. |
246
250
  | `components` | Server component registry. Components may include `schema` and optional `loader`. |
@@ -272,6 +276,43 @@ Loader args match the page-load and revalidation paths:
272
276
  - `weaverse` — server/client object with `weaverse.storefront` compatibility alias.
273
277
  - `context` — request context.
274
278
 
279
+ ### SEO metadata
280
+
281
+ Use the server entry in `generateMetadata()` to map Weaverse `page.seo` into a Next Metadata-compatible object:
282
+
283
+ ```ts
284
+ // app/pages/[handle]/page.tsx
285
+ import { getWeaverseNextSeoMetadata } from '@weaverse/next/server'
286
+
287
+ export async function generateMetadata(props: {
288
+ params: Promise<{ handle: string }>
289
+ searchParams: Promise<Record<string, string | string[] | undefined>>
290
+ }) {
291
+ let { handle } = await props.params
292
+ let weaverse = await getWeaverseServerClient(props.searchParams, `/pages/${handle}`)
293
+ let data = await weaverse.loadPage({ type: 'PAGE', handle })
294
+ return getWeaverseNextSeoMetadata(data)
295
+ }
296
+ ```
297
+
298
+ The helper is pure and does not import `next/*`; its return shape is intentionally structural so the app can type it as `Metadata` if desired.
299
+
300
+ ### Custom pages for sitemap generation
301
+
302
+ `fetchCustomPages()` mirrors Hydrogen's sitemap helper and accepts Next cache knobs:
303
+
304
+ ```ts
305
+ let weaverse = await getWeaverseServerClient(Promise.resolve({}), '/')
306
+ let pages = await weaverse.fetchCustomPages({
307
+ locale: 'en-us',
308
+ limit: 100,
309
+ revalidate: 3600,
310
+ tags: ['weaverse:custom-pages'],
311
+ })
312
+ ```
313
+
314
+ It paginates until the API returns `nextCursor: null`; if a later page fails, it returns the pages already fetched instead of throwing.
315
+
275
316
  ## Rendering in a Next route
276
317
 
277
318
  Typical shape:
@@ -331,6 +372,57 @@ export function WeaversePage({ data }: { data: WeaverseNextLoaderData }) {
331
372
  }
332
373
  ```
333
374
 
375
+ ## Root theme provider
376
+
377
+ Global modules (`Header`, `Footer`, popups, a CSS-var bridge) render once in
378
+ `app/layout.tsx`, outside any route's `WeaverseNextProvider`. Mount
379
+ `WeaverseNextRootProvider` there so `useThemeSettings()` works for them too,
380
+ and so route-level providers share one theme settings store instead of each
381
+ creating their own — matching Hydrogen's root-owned `withWeaverse()` store.
382
+
383
+ ```tsx
384
+ // app/layout.tsx
385
+ import { WeaverseNextRootProvider } from '@weaverse/next'
386
+ import { getWeaverseServerClient } from './weaverse-next/server'
387
+
388
+ export default async function RootLayout({
389
+ children,
390
+ }: {
391
+ children: React.ReactNode
392
+ }) {
393
+ let weaverse = await getWeaverseServerClient(Promise.resolve({}), '/')
394
+ let theme = await weaverse.loadThemeSettings()
395
+
396
+ return (
397
+ <html lang="en">
398
+ <body>
399
+ <WeaverseNextRootProvider
400
+ initialThemeSettings={theme.theme}
401
+ themeSchema={theme.schema}
402
+ >
403
+ <Header />
404
+ {children}
405
+ <Footer />
406
+ </WeaverseNextRootProvider>
407
+ </body>
408
+ </html>
409
+ )
410
+ }
411
+ ```
412
+
413
+ A route's `WeaverseNextProvider` automatically adopts the root store when one
414
+ is mounted above it, instead of creating a second one — so Studio's live
415
+ edits reach both the root modules and the page renderer. The root's initial
416
+ theme is authoritative for SSR: routes should not load their own theme
417
+ settings in the final starter. If a route still carries
418
+ `themeSettings`/`client.themeSettings` (e.g. mid-migration), that data merges
419
+ into the root store in a client-only effect after mount, not during render —
420
+ so SSR always renders the root's value, with no render-phase mutation of the
421
+ shared store. Apps that don't mount `WeaverseNextRootProvider` see no
422
+ behavior change: `WeaverseNextProvider` falls back to creating its own store
423
+ and still renders `themeSettings`/`client.themeSettings` synchronously on
424
+ SSR, same as before.
425
+
334
426
  ## Studio setup
335
427
 
336
428
  Studio has two separate pieces.
@@ -366,6 +458,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
366
458
  }
367
459
  ```
368
460
 
461
+ Mounting it in the root layout (rather than per-page) also covers App Router
462
+ routes that never render `WeaverseNextRenderer`, such as `not-found.tsx` and
463
+ `error.tsx` — Studio needs the bridge script loaded before a page tree
464
+ exists, and the root layout still renders around those routes. This is
465
+ SSR-safe: `WeaverseNextStudioConnect` only touches `document` inside a
466
+ `useEffect`, so it is a no-op during server rendering.
467
+
369
468
  ### 2. Page-level runtime binding
370
469
 
371
470
  `WeaverseNextRenderer` creates and binds the page runtime. Internally, `WeaverseNextStudio` maps Studio internals to Next App Router:
@@ -443,6 +542,5 @@ app/collections/[handle]/page.tsx # collection route Weaverse load
443
542
 
444
543
  - This package is still alpha; API names may change before stable.
445
544
  - Next does not provide Hydrogen's global React Router loader context, so apps should keep a small `getWeaverseServerClient(...)` helper.
545
+ - Translation/static-text sidecar parity, multiple Weaverse runtimes on one URL, and global sections are later hardening items.
446
546
  - 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.
@@ -158,6 +158,62 @@ function buildWeaverseNextRequestInfo(context) {
158
158
  };
159
159
  }
160
160
 
161
- export { buildWeaverseNextRequestInfo, generateDataFromSchema, runWeaverseComponentLoaders };
162
- //# sourceMappingURL=chunk-O2TG2NYL.mjs.map
163
- //# sourceMappingURL=chunk-O2TG2NYL.mjs.map
161
+ // src/seo.ts
162
+ function hasOpenGraphContent(seo) {
163
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.type);
164
+ }
165
+ function hasTwitterContent(seo) {
166
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType);
167
+ }
168
+ function formatWeaverseNextSeoMetadata(seo) {
169
+ let index = seo?.robots?.index !== false;
170
+ let follow = seo?.robots?.follow !== false;
171
+ let metadata = {
172
+ robots: { index, follow }
173
+ };
174
+ if (!seo) {
175
+ return metadata;
176
+ }
177
+ if (seo.title) {
178
+ metadata.title = seo.title;
179
+ }
180
+ if (seo.description) {
181
+ metadata.description = seo.description;
182
+ }
183
+ if (seo.keywords) {
184
+ metadata.keywords = seo.keywords;
185
+ }
186
+ if (seo.canonicalUrl) {
187
+ metadata.alternates = { canonical: seo.canonicalUrl };
188
+ }
189
+ let openGraph = seo.openGraph;
190
+ if (hasOpenGraphContent(openGraph)) {
191
+ metadata.openGraph = {
192
+ title: openGraph?.title,
193
+ description: openGraph?.description,
194
+ images: openGraph?.image ? [openGraph.image] : void 0,
195
+ type: openGraph?.type || "website"
196
+ };
197
+ }
198
+ let twitter = seo.twitter;
199
+ if (hasTwitterContent(twitter)) {
200
+ metadata.twitter = {
201
+ card: twitter?.cardType || "summary",
202
+ title: twitter?.title,
203
+ description: twitter?.description,
204
+ images: twitter?.image ? [twitter.image] : void 0
205
+ };
206
+ }
207
+ return metadata;
208
+ }
209
+ function getWeaverseNextSeoMetadata(data) {
210
+ if (!data) {
211
+ return formatWeaverseNextSeoMetadata(null);
212
+ }
213
+ let page = "items" in data && "id" in data ? data : data.page ?? null;
214
+ return formatWeaverseNextSeoMetadata(page?.seo ?? null);
215
+ }
216
+
217
+ export { buildWeaverseNextRequestInfo, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, runWeaverseComponentLoaders };
218
+ //# sourceMappingURL=chunk-6PMZ77RA.mjs.map
219
+ //# sourceMappingURL=chunk-6PMZ77RA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/loader.ts","../src/request-info.ts","../src/seo.ts"],"names":[],"mappings":"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","file":"chunk-6PMZ77RA.mjs","sourcesContent":["import type { SchemaType } from '@weaverse/schema'\n\n/**\n * Generate default component data from a schema's `settings` inputs.\n *\n * Mirrors Hydrogen's `generateDataFromSchema` (settings → `{ [name]:\n * defaultValue }`) but is kept local so `@weaverse/next` doesn't depend on\n * `@weaverse/hydrogen`. Falls back to the deprecated `inspector` array when a\n * schema predates `settings`.\n */\nexport function generateDataFromSchema(\n schema: SchemaType | undefined\n): Record<string, unknown> {\n let data: Record<string, unknown> = {}\n if (!schema) {\n return data\n }\n\n let groups =\n (schema.settings as {\n inputs?: { name?: string; defaultValue?: unknown }[]\n }[]) ??\n (schema as { inspector?: typeof groups }).inspector ??\n []\n\n for (let group of groups) {\n for (let input of group?.inputs ?? []) {\n let { name, defaultValue } = input\n if (name && defaultValue !== null && defaultValue !== undefined) {\n data[name] = defaultValue\n }\n }\n }\n\n return data\n}\n","import type {\n WeaverseNextClient,\n WeaverseNextCommerceContext,\n WeaverseNextComponent,\n WeaverseNextComponentData,\n WeaverseNextLoaderData,\n WeaverseNextRequestContext,\n} from './types'\nimport { generateDataFromSchema } from './utils'\n\nexport interface RunComponentLoadersArgs {\n client: WeaverseNextClient\n commerce?: WeaverseNextCommerceContext\n context?: WeaverseNextRequestContext\n /** Page payload to walk; defaults to `client.data`. */\n data?: WeaverseNextLoaderData | null\n}\n\nfunction isItemNode(node: unknown): node is WeaverseNextComponentData {\n return (\n typeof node === 'object' &&\n node !== null &&\n typeof (node as WeaverseNextComponentData).type === 'string'\n )\n}\n\nfunction cloneItem(item: WeaverseNextComponentData): WeaverseNextComponentData {\n return {\n ...item,\n children: Array.isArray(item.children)\n ? item.children.map((child) =>\n isItemNode(child) ? cloneItem(child) : { ...child }\n )\n : item.children,\n }\n}\n\n/**\n * Walk a serialized page tree and run each registered component's `loader`,\n * attaching the result to the item as `loaderData`.\n *\n * Loaders receive explicit `commerce.storefront` (and `weaverse.storefront` as\n * a compatibility alias via the client). Children are walked recursively when\n * they are inline item objects; flat `items` arrays are covered by the\n * top-level walk.\n *\n * @returns a cloned `data` object with `loaderData` attached to cloned items.\n */\nexport async function runWeaverseComponentLoaders(\n args: RunComponentLoadersArgs\n): Promise<WeaverseNextLoaderData | null> {\n let { client, context, commerce } = args\n let sourceData = args.data ?? client.data\n if (!sourceData) {\n return null\n }\n if (!sourceData.page.items.length) {\n return sourceData\n }\n\n let data: WeaverseNextLoaderData = {\n ...sourceData,\n page: {\n ...sourceData.page,\n items: sourceData.page.items.map(cloneItem),\n },\n }\n let items = data.page.items\n\n let componentMap = new Map<string, WeaverseNextComponent>()\n for (let component of client.components) {\n if (component?.schema?.type) {\n componentMap.set(component.schema.type, component)\n }\n }\n\n let visited = new Set<string>()\n let resolvedContext = context ?? client.requestContext\n let resolvedCommerce = commerce ?? client.commerce\n\n // Per-call `weaverse` alias. When a request-scoped `commerce` is passed, the\n // `weaverse.storefront` compatibility alias must resolve to *that* storefront,\n // not the client's construction-time commerce. Pilot-style loaders call\n // `weaverse.storefront.query(...)`, so a stale alias would query the wrong\n // (or an undefined) storefront. When no override is given we reuse the client\n // as-is to preserve its prototype getters.\n let resolvedWeaverse: WeaverseNextClient =\n resolvedCommerce === client.commerce\n ? client\n : {\n ...client,\n commerce: resolvedCommerce,\n storefront: resolvedCommerce?.storefront,\n }\n\n async function walk(nodes: WeaverseNextComponentData[]) {\n for (let item of nodes) {\n if (!item || visited.has(item.id)) {\n continue\n }\n visited.add(item.id)\n\n let component = componentMap.get(item.type)\n if (component?.loader) {\n try {\n item.loaderData = await component.loader({\n data: { ...generateDataFromSchema(component.schema), ...item.data },\n weaverse: resolvedWeaverse,\n context: resolvedContext,\n commerce: resolvedCommerce,\n })\n } catch (error) {\n console.warn('❌ Item loader run failed.', item.type, item.id, error)\n }\n }\n\n let children = item.children\n if (Array.isArray(children)) {\n let inlineChildren = children.filter(\n isItemNode\n ) as WeaverseNextComponentData[]\n if (inlineChildren.length) {\n await walk(inlineChildren)\n }\n }\n }\n }\n\n await walk(items)\n return data\n}\n","import type {\n WeaverseNextRequestContext,\n WeaverseNextRequestInfo,\n} from './types'\n\nfunction toUrl(value: string | URL): URL {\n return typeof value === 'string' ? new URL(value, 'http://localhost') : value\n}\n\nfunction getSearchParamsFromContext(\n context?: WeaverseNextRequestContext\n): URLSearchParams {\n if (context?.searchParams) {\n return new URLSearchParams(context.searchParams)\n }\n\n if (context?.url) {\n let url = toUrl(context.url)\n return new URLSearchParams(url.searchParams)\n }\n\n return new URLSearchParams()\n}\n\nfunction getPathnameFromContext(context?: WeaverseNextRequestContext): string {\n if (context?.pathname) {\n return context.pathname\n }\n\n if (context?.url) {\n let url = toUrl(context.url)\n return url.pathname || '/'\n }\n\n return '/'\n}\n\nfunction coerceQueryValue(value: string): string | boolean {\n if (value === 'true') {\n return true\n }\n if (value === 'false') {\n return false\n }\n return value\n}\n\nfunction toQueryRecord(\n searchParams: URLSearchParams\n): Record<string, string | boolean> {\n let queries: Record<string, string | boolean> = {}\n for (let [key, value] of searchParams.entries()) {\n queries[key] = coerceQueryValue(value)\n }\n return queries\n}\n\nconst TRANSIENT_STUDIO_PARAMS = new Set([\n 'weaverseDraftItem',\n // Legacy name from the original design-mode draft-item spike. Keep it out of\n // runtime requestInfo too, in case an older Builder bundle is in front of a\n // newer Next storefront.\n '__weaverseDraftItem',\n])\n\nfunction getRequestInfoSearchParams(\n context?: WeaverseNextRequestContext\n): URLSearchParams {\n let searchParams = new URLSearchParams(getSearchParamsFromContext(context))\n for (let key of TRANSIENT_STUDIO_PARAMS) {\n searchParams.delete(key)\n }\n return searchParams\n}\n\n/**\n * Build the Hydrogen-like request info shape consumed by the Studio bridge.\n * Duplicate query keys keep the last value, matching `URLSearchParams` iteration\n * assignment and Hydrogen's practical query-object behavior.\n */\nexport function buildWeaverseNextRequestInfo(\n context?: WeaverseNextRequestContext\n): WeaverseNextRequestInfo {\n let searchParams = getRequestInfoSearchParams(context)\n let search = searchParams.toString()\n\n return {\n pathname: getPathnameFromContext(context),\n search: search ? `?${search}` : '',\n queries: toQueryRecord(searchParams),\n ...(context?.i18n ? { i18n: context.i18n } : {}),\n }\n}\n","import type { PageSEOData } from '@weaverse/schema'\nimport type { WeaverseNextLoaderData, WeaverseNextPageData } from './types'\n\nexport interface WeaverseNextSeoMetadata {\n alternates?: { canonical?: string }\n description?: string\n keywords?: string\n openGraph?: {\n description?: string\n images?: string[]\n title?: string\n type?: string\n }\n robots: {\n follow: boolean\n index: boolean\n }\n title?: string\n twitter?: {\n card?: string\n description?: string\n images?: string[]\n title?: string\n }\n}\n\nfunction hasOpenGraphContent(seo: PageSEOData['openGraph']): boolean {\n return Boolean(seo?.title || seo?.description || seo?.image || seo?.type)\n}\n\nfunction hasTwitterContent(seo: PageSEOData['twitter']): boolean {\n return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType)\n}\n\nexport function formatWeaverseNextSeoMetadata(\n seo: PageSEOData | null | undefined\n): WeaverseNextSeoMetadata {\n let index = seo?.robots?.index !== false\n let follow = seo?.robots?.follow !== false\n let metadata: WeaverseNextSeoMetadata = {\n robots: { index, follow },\n }\n\n if (!seo) {\n return metadata\n }\n\n if (seo.title) {\n metadata.title = seo.title\n }\n if (seo.description) {\n metadata.description = seo.description\n }\n if (seo.keywords) {\n metadata.keywords = seo.keywords\n }\n if (seo.canonicalUrl) {\n metadata.alternates = { canonical: seo.canonicalUrl }\n }\n\n let openGraph = seo.openGraph\n if (hasOpenGraphContent(openGraph)) {\n metadata.openGraph = {\n title: openGraph?.title,\n description: openGraph?.description,\n images: openGraph?.image ? [openGraph.image] : undefined,\n type: openGraph?.type || 'website',\n }\n }\n\n let twitter = seo.twitter\n if (hasTwitterContent(twitter)) {\n metadata.twitter = {\n card: twitter?.cardType || 'summary',\n title: twitter?.title,\n description: twitter?.description,\n images: twitter?.image ? [twitter.image] : undefined,\n }\n }\n\n return metadata\n}\n\nexport function getWeaverseNextSeoMetadata(\n data:\n | WeaverseNextLoaderData\n | WeaverseNextPageData\n | { page?: WeaverseNextPageData | null }\n | null\n | undefined\n): WeaverseNextSeoMetadata {\n if (!data) {\n return formatWeaverseNextSeoMetadata(null)\n }\n let page =\n 'items' in data && 'id' in data\n ? (data as WeaverseNextPageData)\n : ((data as { page?: WeaverseNextPageData | null }).page ?? null)\n return formatWeaverseNextSeoMetadata(page?.seo ?? null)\n}\n"]}
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Weaverse } from '@weaverse/react';
2
2
  export { isBrowser, isIframe, useChildInstances, useItemInstance, useParentInstance, useWeaverse } from '@weaverse/react';
3
3
  export { InspectorGroup, PageType, SchemaType, createSchema } from '@weaverse/schema';
4
- import { d as WeaverseNextClientConfig, c as WeaverseNextClient, e as WeaverseNextCommerceContext, q as WeaverseNextRequestContext, n as WeaverseNextLoaderData, r as WeaverseNextRequestInfo, g as WeaverseNextComponentData, t as WeaverseNextRuntimeInternal, s as WeaverseNextRuntimeConfig, A as WeaverseNextThemeSettingsStore } from './types-DvwaU20C.mjs';
5
- export { W as WeaverseCollection, f as WeaverseNextComponent, h as WeaverseNextComponentLoaderArgs, i as WeaverseNextComponentProps, l as WeaverseNextI18n, m as WeaverseNextLoadPageInput, o as WeaverseNextPageData, x as WeaverseNextStorefront, B as WeaverseProduct } from './types-DvwaU20C.mjs';
4
+ import { g as WeaverseNextClientConfig, f as WeaverseNextClient, h as WeaverseNextCommerceContext, v as WeaverseNextRequestContext, s as WeaverseNextLoaderData, w as WeaverseNextRequestInfo, j as WeaverseNextComponentData, G as WeaverseNextThemeSettingsStore, y as WeaverseNextRuntimeInternal, x as WeaverseNextRuntimeConfig } from './seo-kwaliD-K.mjs';
5
+ export { W as WeaverseArticle, a as WeaverseBlog, b as WeaverseCollection, c as WeaverseMetaObject, i as WeaverseNextComponent, k as WeaverseNextComponentLoaderArgs, l as WeaverseNextComponentProps, n as WeaverseNextCustomPageEntry, o as WeaverseNextFetchCustomPagesOptions, q as WeaverseNextI18n, r as WeaverseNextLoadPageInput, t as WeaverseNextPageData, z as WeaverseNextSeoMetadata, D as WeaverseNextStorefront, H as WeaverseProduct, I as formatWeaverseNextSeoMetadata, J as getWeaverseNextSeoMetadata } from './seo-kwaliD-K.mjs';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
  import * as react from 'react';
8
8
  import { ReactNode } from 'react';
@@ -143,6 +143,37 @@ interface RevalidateItemRuntimeLike {
143
143
  */
144
144
  declare function revalidateWeaverseNextItem(runtime: RevalidateItemRuntimeLike, draftItem: WeaverseNextComponentData, endpoint?: string): Promise<void>;
145
145
 
146
+ interface WeaverseNextRootContextValue {
147
+ themeSettingsStore: WeaverseNextThemeSettingsStore;
148
+ }
149
+ interface WeaverseNextRootProviderProps {
150
+ children: ReactNode;
151
+ /** Root-loaded theme settings (published-mode fetch, no design-mode context). */
152
+ initialThemeSettings?: Record<string, unknown>;
153
+ publicEnv?: Record<string, string | undefined>;
154
+ themeSchema?: unknown;
155
+ }
156
+ /**
157
+ * Root-scoped client boundary that owns the single `WeaverseNextThemeSettingsStore`
158
+ * for the whole app session, matching Hydrogen's `withWeaverse()` root store.
159
+ * Mount once in `app/layout.tsx`, above `Header`/`Footer`/global modules and any
160
+ * route-level `WeaverseNextProvider`.
161
+ *
162
+ * Also renders `WeaverseNextContext` with a minimal value so `useThemeSettings()`
163
+ * works for components mounted directly under this provider, even without a
164
+ * route-level `WeaverseNextProvider` above them.
165
+ *
166
+ * @example
167
+ * ```tsx
168
+ * <WeaverseNextRootProvider initialThemeSettings={theme.theme} themeSchema={theme.schema}>
169
+ * <Header />
170
+ * {children}
171
+ * <Footer />
172
+ * </WeaverseNextRootProvider>
173
+ * ```
174
+ */
175
+ declare function WeaverseNextRootProvider(props: WeaverseNextRootProviderProps): react_jsx_runtime.JSX.Element;
176
+
146
177
  declare global {
147
178
  interface Window {
148
179
  __weaverse?: WeaverseNextRuntime;
@@ -177,11 +208,25 @@ interface WeaverseNextStudioBridgeProps {
177
208
  /** Page-level Studio runtime binder. */
178
209
  declare function WeaverseNextStudioBridge(props: WeaverseNextStudioBridgeProps): null;
179
210
 
211
+ interface LoadWeaverseNextStudioScriptOptions {
212
+ storefrontHostname?: string;
213
+ }
214
+ /**
215
+ * Resolve and append the Next Studio bridge script for the given request
216
+ * context, deduping by src. SSR-safe (no-op without `document`) so it can run
217
+ * unconditionally from a root layout/error boundary before any page tree or
218
+ * runtime exists — e.g. App Router `not-found.tsx` / `error.tsx` routes.
219
+ */
220
+ declare function loadWeaverseNextStudioScript(context?: WeaverseNextRequestContext, options?: LoadWeaverseNextStudioScriptOptions): void;
180
221
  interface WeaverseNextStudioConnectProps {
181
222
  context?: WeaverseNextRequestContext;
182
223
  storefrontHostname?: string;
183
224
  }
184
- /** Root-level Studio script connector for Next client boundaries. */
225
+ /**
226
+ * Root-level Studio script connector for Next client boundaries. Mount once
227
+ * near the app root (e.g. root layout) so it also covers routes that never
228
+ * render `WeaverseNextRenderer`, such as `not-found.tsx` / `error.tsx`.
229
+ */
185
230
  declare function WeaverseNextStudioConnect(props: WeaverseNextStudioConnectProps): null;
186
231
 
187
232
  /**
@@ -276,4 +321,4 @@ interface WeaverseNextStudioProps extends CreateWeaverseNextStudioInternalsOptio
276
321
  */
277
322
  declare function WeaverseNextStudio(props: WeaverseNextStudioProps): react_jsx_runtime.JSX.Element;
278
323
 
279
- export { type CreateWeaverseNextStudioInternalsOptions, type CreateWeaverseNextThemeSettingsStoreOptions, DEFAULT_REVALIDATE_ENDPOINT, type ResolveWeaverseNextStudioScriptSrcOptions, type RevalidateItemRuntimeLike, type RunComponentLoadersArgs, WeaverseNextClient, WeaverseNextClientConfig, WeaverseNextCommerceContext, WeaverseNextComponentData, WeaverseNextLoaderData, WeaverseNextProvider, type WeaverseNextProviderProps, WeaverseNextRenderer, type WeaverseNextRendererProps, WeaverseNextRequestContext, WeaverseNextRequestInfo, type WeaverseNextRouterLike, WeaverseNextRuntime, WeaverseNextRuntimeConfig, WeaverseNextRuntimeInternal, WeaverseNextStudio, WeaverseNextStudioBridge, type WeaverseNextStudioBridgeProps, WeaverseNextStudioConnect, type WeaverseNextStudioConnectProps, type WeaverseNextStudioInternals, type WeaverseNextStudioProps, WeaverseNextThemeSettingsStore, bindWeaverseNextStudioRuntime, buildWeaverseNextRequestInfo, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, runWeaverseComponentLoaders, useThemeSettings, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
324
+ export { type CreateWeaverseNextStudioInternalsOptions, type CreateWeaverseNextThemeSettingsStoreOptions, DEFAULT_REVALIDATE_ENDPOINT, type LoadWeaverseNextStudioScriptOptions, type ResolveWeaverseNextStudioScriptSrcOptions, type RevalidateItemRuntimeLike, type RunComponentLoadersArgs, WeaverseNextClient, WeaverseNextClientConfig, WeaverseNextCommerceContext, WeaverseNextComponentData, WeaverseNextLoaderData, WeaverseNextProvider, type WeaverseNextProviderProps, WeaverseNextRenderer, type WeaverseNextRendererProps, WeaverseNextRequestContext, WeaverseNextRequestInfo, type WeaverseNextRootContextValue, WeaverseNextRootProvider, type WeaverseNextRootProviderProps, type WeaverseNextRouterLike, WeaverseNextRuntime, WeaverseNextRuntimeConfig, WeaverseNextRuntimeInternal, WeaverseNextStudio, WeaverseNextStudioBridge, type WeaverseNextStudioBridgeProps, WeaverseNextStudioConnect, type WeaverseNextStudioConnectProps, type WeaverseNextStudioInternals, type WeaverseNextStudioProps, WeaverseNextThemeSettingsStore, bindWeaverseNextStudioRuntime, buildWeaverseNextRequestInfo, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, runWeaverseComponentLoaders, useThemeSettings, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Weaverse } from '@weaverse/react';
2
2
  export { isBrowser, isIframe, useChildInstances, useItemInstance, useParentInstance, useWeaverse } from '@weaverse/react';
3
3
  export { InspectorGroup, PageType, SchemaType, createSchema } from '@weaverse/schema';
4
- import { d as WeaverseNextClientConfig, c as WeaverseNextClient, e as WeaverseNextCommerceContext, q as WeaverseNextRequestContext, n as WeaverseNextLoaderData, r as WeaverseNextRequestInfo, g as WeaverseNextComponentData, t as WeaverseNextRuntimeInternal, s as WeaverseNextRuntimeConfig, A as WeaverseNextThemeSettingsStore } from './types-DvwaU20C.js';
5
- export { W as WeaverseCollection, f as WeaverseNextComponent, h as WeaverseNextComponentLoaderArgs, i as WeaverseNextComponentProps, l as WeaverseNextI18n, m as WeaverseNextLoadPageInput, o as WeaverseNextPageData, x as WeaverseNextStorefront, B as WeaverseProduct } from './types-DvwaU20C.js';
4
+ import { g as WeaverseNextClientConfig, f as WeaverseNextClient, h as WeaverseNextCommerceContext, v as WeaverseNextRequestContext, s as WeaverseNextLoaderData, w as WeaverseNextRequestInfo, j as WeaverseNextComponentData, G as WeaverseNextThemeSettingsStore, y as WeaverseNextRuntimeInternal, x as WeaverseNextRuntimeConfig } from './seo-kwaliD-K.js';
5
+ export { W as WeaverseArticle, a as WeaverseBlog, b as WeaverseCollection, c as WeaverseMetaObject, i as WeaverseNextComponent, k as WeaverseNextComponentLoaderArgs, l as WeaverseNextComponentProps, n as WeaverseNextCustomPageEntry, o as WeaverseNextFetchCustomPagesOptions, q as WeaverseNextI18n, r as WeaverseNextLoadPageInput, t as WeaverseNextPageData, z as WeaverseNextSeoMetadata, D as WeaverseNextStorefront, H as WeaverseProduct, I as formatWeaverseNextSeoMetadata, J as getWeaverseNextSeoMetadata } from './seo-kwaliD-K.js';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
  import * as react from 'react';
8
8
  import { ReactNode } from 'react';
@@ -143,6 +143,37 @@ interface RevalidateItemRuntimeLike {
143
143
  */
144
144
  declare function revalidateWeaverseNextItem(runtime: RevalidateItemRuntimeLike, draftItem: WeaverseNextComponentData, endpoint?: string): Promise<void>;
145
145
 
146
+ interface WeaverseNextRootContextValue {
147
+ themeSettingsStore: WeaverseNextThemeSettingsStore;
148
+ }
149
+ interface WeaverseNextRootProviderProps {
150
+ children: ReactNode;
151
+ /** Root-loaded theme settings (published-mode fetch, no design-mode context). */
152
+ initialThemeSettings?: Record<string, unknown>;
153
+ publicEnv?: Record<string, string | undefined>;
154
+ themeSchema?: unknown;
155
+ }
156
+ /**
157
+ * Root-scoped client boundary that owns the single `WeaverseNextThemeSettingsStore`
158
+ * for the whole app session, matching Hydrogen's `withWeaverse()` root store.
159
+ * Mount once in `app/layout.tsx`, above `Header`/`Footer`/global modules and any
160
+ * route-level `WeaverseNextProvider`.
161
+ *
162
+ * Also renders `WeaverseNextContext` with a minimal value so `useThemeSettings()`
163
+ * works for components mounted directly under this provider, even without a
164
+ * route-level `WeaverseNextProvider` above them.
165
+ *
166
+ * @example
167
+ * ```tsx
168
+ * <WeaverseNextRootProvider initialThemeSettings={theme.theme} themeSchema={theme.schema}>
169
+ * <Header />
170
+ * {children}
171
+ * <Footer />
172
+ * </WeaverseNextRootProvider>
173
+ * ```
174
+ */
175
+ declare function WeaverseNextRootProvider(props: WeaverseNextRootProviderProps): react_jsx_runtime.JSX.Element;
176
+
146
177
  declare global {
147
178
  interface Window {
148
179
  __weaverse?: WeaverseNextRuntime;
@@ -177,11 +208,25 @@ interface WeaverseNextStudioBridgeProps {
177
208
  /** Page-level Studio runtime binder. */
178
209
  declare function WeaverseNextStudioBridge(props: WeaverseNextStudioBridgeProps): null;
179
210
 
211
+ interface LoadWeaverseNextStudioScriptOptions {
212
+ storefrontHostname?: string;
213
+ }
214
+ /**
215
+ * Resolve and append the Next Studio bridge script for the given request
216
+ * context, deduping by src. SSR-safe (no-op without `document`) so it can run
217
+ * unconditionally from a root layout/error boundary before any page tree or
218
+ * runtime exists — e.g. App Router `not-found.tsx` / `error.tsx` routes.
219
+ */
220
+ declare function loadWeaverseNextStudioScript(context?: WeaverseNextRequestContext, options?: LoadWeaverseNextStudioScriptOptions): void;
180
221
  interface WeaverseNextStudioConnectProps {
181
222
  context?: WeaverseNextRequestContext;
182
223
  storefrontHostname?: string;
183
224
  }
184
- /** Root-level Studio script connector for Next client boundaries. */
225
+ /**
226
+ * Root-level Studio script connector for Next client boundaries. Mount once
227
+ * near the app root (e.g. root layout) so it also covers routes that never
228
+ * render `WeaverseNextRenderer`, such as `not-found.tsx` / `error.tsx`.
229
+ */
185
230
  declare function WeaverseNextStudioConnect(props: WeaverseNextStudioConnectProps): null;
186
231
 
187
232
  /**
@@ -276,4 +321,4 @@ interface WeaverseNextStudioProps extends CreateWeaverseNextStudioInternalsOptio
276
321
  */
277
322
  declare function WeaverseNextStudio(props: WeaverseNextStudioProps): react_jsx_runtime.JSX.Element;
278
323
 
279
- export { type CreateWeaverseNextStudioInternalsOptions, type CreateWeaverseNextThemeSettingsStoreOptions, DEFAULT_REVALIDATE_ENDPOINT, type ResolveWeaverseNextStudioScriptSrcOptions, type RevalidateItemRuntimeLike, type RunComponentLoadersArgs, WeaverseNextClient, WeaverseNextClientConfig, WeaverseNextCommerceContext, WeaverseNextComponentData, WeaverseNextLoaderData, WeaverseNextProvider, type WeaverseNextProviderProps, WeaverseNextRenderer, type WeaverseNextRendererProps, WeaverseNextRequestContext, WeaverseNextRequestInfo, type WeaverseNextRouterLike, WeaverseNextRuntime, WeaverseNextRuntimeConfig, WeaverseNextRuntimeInternal, WeaverseNextStudio, WeaverseNextStudioBridge, type WeaverseNextStudioBridgeProps, WeaverseNextStudioConnect, type WeaverseNextStudioConnectProps, type WeaverseNextStudioInternals, type WeaverseNextStudioProps, WeaverseNextThemeSettingsStore, bindWeaverseNextStudioRuntime, buildWeaverseNextRequestInfo, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, runWeaverseComponentLoaders, useThemeSettings, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
324
+ export { type CreateWeaverseNextStudioInternalsOptions, type CreateWeaverseNextThemeSettingsStoreOptions, DEFAULT_REVALIDATE_ENDPOINT, type LoadWeaverseNextStudioScriptOptions, type ResolveWeaverseNextStudioScriptSrcOptions, type RevalidateItemRuntimeLike, type RunComponentLoadersArgs, WeaverseNextClient, WeaverseNextClientConfig, WeaverseNextCommerceContext, WeaverseNextComponentData, WeaverseNextLoaderData, WeaverseNextProvider, type WeaverseNextProviderProps, WeaverseNextRenderer, type WeaverseNextRendererProps, WeaverseNextRequestContext, WeaverseNextRequestInfo, type WeaverseNextRootContextValue, WeaverseNextRootProvider, type WeaverseNextRootProviderProps, type WeaverseNextRouterLike, WeaverseNextRuntime, WeaverseNextRuntimeConfig, WeaverseNextRuntimeInternal, WeaverseNextStudio, WeaverseNextStudioBridge, type WeaverseNextStudioBridgeProps, WeaverseNextStudioConnect, type WeaverseNextStudioConnectProps, type WeaverseNextStudioInternals, type WeaverseNextStudioProps, WeaverseNextThemeSettingsStore, bindWeaverseNextStudioRuntime, buildWeaverseNextRequestInfo, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, runWeaverseComponentLoaders, useThemeSettings, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
package/dist/index.js CHANGED
@@ -158,18 +158,61 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
158
158
  };
159
159
  return store;
160
160
  }
161
+ var WeaverseNextRootContext = react$1.createContext(null);
162
+ function WeaverseNextRootProvider(props) {
163
+ let { children, initialThemeSettings, themeSchema, publicEnv } = props;
164
+ let storeRef = react$1.useRef(null);
165
+ if (!storeRef.current) {
166
+ storeRef.current = createWeaverseNextThemeSettingsStore({
167
+ publicEnv,
168
+ schema: themeSchema,
169
+ settings: initialThemeSettings
170
+ });
171
+ }
172
+ let themeSettingsStore = storeRef.current;
173
+ let appliedThemeSettingsRef = react$1.useRef(initialThemeSettings);
174
+ react$1.useEffect(() => {
175
+ if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
176
+ themeSettingsStore.updateThemeSettings(initialThemeSettings);
177
+ appliedThemeSettingsRef.current = initialThemeSettings;
178
+ }
179
+ }, [initialThemeSettings, themeSettingsStore]);
180
+ let rootContextValue = react$1.useMemo(
181
+ () => ({ themeSettingsStore }),
182
+ [themeSettingsStore]
183
+ );
184
+ let contextValue = react$1.useMemo(
185
+ () => ({
186
+ themeSettings: themeSettingsStore.getSnapshot(),
187
+ themeSettingsStore
188
+ }),
189
+ [themeSettingsStore]
190
+ );
191
+ return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value: contextValue, children }) });
192
+ }
161
193
  var EMPTY_THEME_SETTINGS = {};
162
194
  var WeaverseNextContext = react$1.createContext(null);
163
195
  function WeaverseNextProvider(props) {
164
196
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
197
+ let rootContext = react$1.useContext(WeaverseNextRootContext);
165
198
  let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
166
- let themeSettingsStore = react$1.useMemo(
167
- () => createWeaverseNextThemeSettingsStore({
199
+ let ownThemeSettingsStore = react$1.useMemo(() => {
200
+ if (rootContext) {
201
+ return null;
202
+ }
203
+ return createWeaverseNextThemeSettingsStore({
168
204
  schema: client?.themeSchema,
169
205
  settings: themeSettingsValue
170
- }),
171
- [client?.themeSchema, themeSettingsValue]
172
- );
206
+ });
207
+ }, [rootContext, client?.themeSchema, themeSettingsValue]);
208
+ let themeSettingsStore = rootContext?.themeSettingsStore ?? ownThemeSettingsStore;
209
+ let mergedIntoRootRef = react$1.useRef(void 0);
210
+ react$1.useEffect(() => {
211
+ if (rootContext && themeSettingsValue !== EMPTY_THEME_SETTINGS && mergedIntoRootRef.current !== themeSettingsValue) {
212
+ rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue);
213
+ mergedIntoRootRef.current = themeSettingsValue;
214
+ }
215
+ }, [rootContext, themeSettingsValue]);
173
216
  let value = react$1.useMemo(
174
217
  () => ({
175
218
  client,
@@ -675,6 +718,62 @@ var WeaverseNextRenderer = react$1.memo(function WeaverseNextRendererComponent(p
675
718
  ] });
676
719
  });
677
720
 
721
+ // src/seo.ts
722
+ function hasOpenGraphContent(seo) {
723
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.type);
724
+ }
725
+ function hasTwitterContent(seo) {
726
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType);
727
+ }
728
+ function formatWeaverseNextSeoMetadata(seo) {
729
+ let index = seo?.robots?.index !== false;
730
+ let follow = seo?.robots?.follow !== false;
731
+ let metadata = {
732
+ robots: { index, follow }
733
+ };
734
+ if (!seo) {
735
+ return metadata;
736
+ }
737
+ if (seo.title) {
738
+ metadata.title = seo.title;
739
+ }
740
+ if (seo.description) {
741
+ metadata.description = seo.description;
742
+ }
743
+ if (seo.keywords) {
744
+ metadata.keywords = seo.keywords;
745
+ }
746
+ if (seo.canonicalUrl) {
747
+ metadata.alternates = { canonical: seo.canonicalUrl };
748
+ }
749
+ let openGraph = seo.openGraph;
750
+ if (hasOpenGraphContent(openGraph)) {
751
+ metadata.openGraph = {
752
+ title: openGraph?.title,
753
+ description: openGraph?.description,
754
+ images: openGraph?.image ? [openGraph.image] : void 0,
755
+ type: openGraph?.type || "website"
756
+ };
757
+ }
758
+ let twitter = seo.twitter;
759
+ if (hasTwitterContent(twitter)) {
760
+ metadata.twitter = {
761
+ card: twitter?.cardType || "summary",
762
+ title: twitter?.title,
763
+ description: twitter?.description,
764
+ images: twitter?.image ? [twitter.image] : void 0
765
+ };
766
+ }
767
+ return metadata;
768
+ }
769
+ function getWeaverseNextSeoMetadata(data) {
770
+ if (!data) {
771
+ return formatWeaverseNextSeoMetadata(null);
772
+ }
773
+ let page = "items" in data && "id" in data ? data : data.page ?? null;
774
+ return formatWeaverseNextSeoMetadata(page?.seo ?? null);
775
+ }
776
+
678
777
  // src/studio-script-src.ts
679
778
  var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
680
779
  function isLoopbackHostname(hostname) {
@@ -764,21 +863,26 @@ function loadScript(src) {
764
863
  document.head.appendChild(script);
765
864
  loadedScriptSrcs.add(src);
766
865
  }
866
+ function loadWeaverseNextStudioScript(context, options = {}) {
867
+ let resolvedContext = context;
868
+ if (!resolvedContext && typeof window !== "undefined") {
869
+ resolvedContext = {
870
+ searchParams: new URLSearchParams(window.location.search),
871
+ url: window.location.href
872
+ };
873
+ }
874
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
875
+ storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
876
+ });
877
+ if (src) {
878
+ loadScript(src);
879
+ }
880
+ }
767
881
  function WeaverseNextStudioConnect(props) {
768
882
  react$1.useEffect(() => {
769
- let context = props.context;
770
- if (!context && typeof window !== "undefined") {
771
- context = {
772
- searchParams: new URLSearchParams(window.location.search),
773
- url: window.location.href
774
- };
775
- }
776
- let src = resolveWeaverseNextStudioScriptSrc(context, {
777
- storefrontHostname: props.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
883
+ loadWeaverseNextStudioScript(props.context, {
884
+ storefrontHostname: props.storefrontHostname
778
885
  });
779
- if (src) {
780
- loadScript(src);
781
- }
782
886
  }, [props.context, props.storefrontHostname]);
783
887
  return null;
784
888
  }
@@ -814,6 +918,7 @@ Object.defineProperty(exports, "createSchema", {
814
918
  exports.DEFAULT_REVALIDATE_ENDPOINT = DEFAULT_REVALIDATE_ENDPOINT;
815
919
  exports.WeaverseNextProvider = WeaverseNextProvider;
816
920
  exports.WeaverseNextRenderer = WeaverseNextRenderer;
921
+ exports.WeaverseNextRootProvider = WeaverseNextRootProvider;
817
922
  exports.WeaverseNextRuntime = WeaverseNextRuntime;
818
923
  exports.WeaverseNextStudio = WeaverseNextStudio;
819
924
  exports.WeaverseNextStudioBridge = WeaverseNextStudioBridge;
@@ -824,6 +929,9 @@ exports.createWeaverseNextClient = createWeaverseNextClient;
824
929
  exports.createWeaverseNextRuntime = createWeaverseNextRuntime;
825
930
  exports.createWeaverseNextStudioInternals = createWeaverseNextStudioInternals;
826
931
  exports.createWeaverseNextThemeSettingsStore = createWeaverseNextThemeSettingsStore;
932
+ exports.formatWeaverseNextSeoMetadata = formatWeaverseNextSeoMetadata;
933
+ exports.getWeaverseNextSeoMetadata = getWeaverseNextSeoMetadata;
934
+ exports.loadWeaverseNextStudioScript = loadWeaverseNextStudioScript;
827
935
  exports.resolveWeaverseNextStudioScriptSrc = resolveWeaverseNextStudioScriptSrc;
828
936
  exports.revalidateWeaverseNextItem = revalidateWeaverseNextItem;
829
937
  exports.runWeaverseComponentLoaders = runWeaverseComponentLoaders;