@nextsparkjs/core 0.1.0-beta.134 → 0.1.0-beta.135

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.
@@ -1,13 +1,18 @@
1
1
  /**
2
- * PageRenderer Component
2
+ * PageRenderer Component — Async Server Component with RSC Streaming
3
3
  *
4
- * Renders pages from the Page Builder by iterating over blocks
5
- * and loading block components from the SSR registry.
4
+ * Renders pages from the Page Builder using async dynamic imports
5
+ * per block, wrapped in Suspense boundaries for streaming SSR.
6
6
  *
7
- * Block components are loaded via next/dynamic (ssr: true) for per-block
8
- * code splitting. Each block gets its own JS chunk only blocks used on
9
- * the page have their JS loaded by the browser. SSR renders full HTML
10
- * so content is visible without client JS (SEO-safe).
7
+ * How it works:
8
+ * 1. Each block is loaded via `loadBlockSSR(slug)`an async import
9
+ * 2. React Server Components resolve the import on the server
10
+ * 3. Suspense boundaries let React stream HTML progressively
11
+ * 4. Client receives complete HTML (zero CLS, SEO-safe)
12
+ * 5. Only JS chunks for blocks actually on the page are sent to client
13
+ *
14
+ * This gives per-block code splitting WITHOUT the CLS issues of next/dynamic,
15
+ * because the server fully resolves each block before flushing the HTML.
11
16
  *
12
17
  * @module core/components/public/pageBuilder
13
18
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PageRenderer.d.ts","sourceRoot":"","sources":["../../../../src/components/public/pageBuilder/PageRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAsC1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,aAAa,EAAE,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,iBAAiB,2CA+BvD"}
1
+ {"version":3,"file":"PageRenderer.d.ts","sourceRoot":"","sources":["../../../../src/components/public/pageBuilder/PageRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAqC1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,aAAa,EAAE,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,iBAAiB,2CAiCvD"}
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Suspense } from "react";
3
- import { getBlockComponentSSR, normalizeBlockProps } from "../../../lib/blocks/loader.js";
3
+ import { loadBlockSSR, normalizeBlockProps } from "../../../lib/blocks/loader.js";
4
4
  function BlockError({ blockSlug }) {
5
5
  return /* @__PURE__ */ jsx("div", { className: "w-full py-12 px-4 bg-destructive/10 border border-destructive/20 rounded", children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto text-center", children: [
6
6
  /* @__PURE__ */ jsxs("p", { className: "text-destructive", children: [
@@ -10,14 +10,14 @@ function BlockError({ blockSlug }) {
10
10
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "This block may not be available or there was an error rendering it." })
11
11
  ] }) });
12
12
  }
13
- function BlockRenderer({ block }) {
14
- const BlockComponent = getBlockComponentSSR(block.blockSlug);
13
+ async function BlockRenderer({ block }) {
14
+ const BlockComponent = await loadBlockSSR(block.blockSlug);
15
15
  if (!BlockComponent) {
16
16
  console.warn(`Block component not found for slug: ${block.blockSlug}`);
17
17
  return /* @__PURE__ */ jsx(BlockError, { blockSlug: block.blockSlug });
18
18
  }
19
19
  const normalizedProps = normalizeBlockProps(block.props);
20
- return /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(BlockComponent, { ...normalizedProps }) });
20
+ return /* @__PURE__ */ jsx(BlockComponent, { ...normalizedProps });
21
21
  }
22
22
  function PageRenderer({ page }) {
23
23
  const blocks = Array.isArray(page.blocks) ? page.blocks : [];
@@ -33,7 +33,7 @@ function PageRenderer({ page }) {
33
33
  className: "@container w-full",
34
34
  "data-block-id": block.id,
35
35
  "data-block-slug": block.blockSlug,
36
- children: /* @__PURE__ */ jsx(BlockRenderer, { block })
36
+ children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(BlockRenderer, { block }) })
37
37
  },
38
38
  block.id
39
39
  )) });
@@ -23,14 +23,26 @@ export declare function getBlockComponents(): Record<string, BlockComponent>;
23
23
  */
24
24
  export declare function getBlockComponent(slug: string): BlockComponent | undefined;
25
25
  /**
26
- * Get all SSR block components (direct imports, no React.lazy)
27
- * Use for public page rendering where no-JS SSR is required
26
+ * @deprecated Use loadBlockSSR for async Server Component rendering
28
27
  */
29
28
  export declare function getBlockComponentsSSR(): Record<string, BlockComponent>;
30
29
  /**
31
- * Get a specific SSR block component by slug (direct import, no React.lazy)
30
+ * @deprecated Use loadBlockSSR for async Server Component rendering
32
31
  */
33
32
  export declare function getBlockComponentSSR(slug: string): BlockComponent | undefined;
33
+ /**
34
+ * Async block loader for RSC streaming.
35
+ *
36
+ * Dynamically imports a block component by slug. Used by PageRenderer
37
+ * (async Server Component) with Suspense for:
38
+ * - Per-block code splitting (only used blocks ship JS to client)
39
+ * - Streaming SSR (server flushes HTML progressively)
40
+ * - Zero CLS (HTML complete before client JS runs)
41
+ *
42
+ * @param slug - Block slug (e.g., 'hero', 'accordion')
43
+ * @returns Resolved block component, or undefined if not found
44
+ */
45
+ export declare function loadBlockSSR(slug: string): Promise<BlockComponent | undefined>;
34
46
  /**
35
47
  * Check if a block exists in the registry
36
48
  *
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAIrC,KAAK,cAAc,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;AAExC;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE7E;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgD3F"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAIrC,KAAK,cAAc,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;AAExC;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAKpF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgD3F"}
@@ -1,4 +1,4 @@
1
- import { BLOCK_COMPONENTS, BLOCK_COMPONENTS_SSR } from "@nextsparkjs/registries/block-registry";
1
+ import { BLOCK_COMPONENTS, BLOCK_COMPONENTS_SSR, BLOCK_IMPORTS_SSR } from "@nextsparkjs/registries/block-registry";
2
2
  function getBlockComponents() {
3
3
  return BLOCK_COMPONENTS;
4
4
  }
@@ -11,8 +11,14 @@ function getBlockComponentsSSR() {
11
11
  function getBlockComponentSSR(slug) {
12
12
  return BLOCK_COMPONENTS_SSR[slug];
13
13
  }
14
+ async function loadBlockSSR(slug) {
15
+ const importFn = BLOCK_IMPORTS_SSR[slug];
16
+ if (!importFn) return void 0;
17
+ const mod = await importFn();
18
+ return mod.default;
19
+ }
14
20
  function hasBlock(slug) {
15
- return slug in BLOCK_COMPONENTS;
21
+ return slug in BLOCK_COMPONENTS || slug in BLOCK_IMPORTS_SSR;
16
22
  }
17
23
  function normalizeBlockProps(props) {
18
24
  if (!props || typeof props !== "object" || Array.isArray(props)) {
@@ -58,5 +64,6 @@ export {
58
64
  getBlockComponents,
59
65
  getBlockComponentsSSR,
60
66
  hasBlock,
67
+ loadBlockSSR,
61
68
  normalizeBlockProps
62
69
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-04-07T15:29:52.891Z",
2
+ "generated": "2026-04-07T17:07:35.712Z",
3
3
  "totalClasses": 1078,
4
4
  "classes": [
5
5
  "!text-2xl",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.134",
3
+ "version": "0.1.0-beta.135",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -462,7 +462,7 @@
462
462
  "tailwind-merge": "^3.3.1",
463
463
  "uuid": "^13.0.0",
464
464
  "zod": "^4.1.5",
465
- "@nextsparkjs/testing": "0.1.0-beta.134"
465
+ "@nextsparkjs/testing": "0.1.0-beta.135"
466
466
  },
467
467
  "scripts": {
468
468
  "postinstall": "node scripts/postinstall.mjs || true",
@@ -47,10 +47,11 @@ export const BLOCK_CATEGORIES: string[] = []
47
47
 
48
48
  export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<unknown>>> = {}
49
49
 
50
- /**
51
- * Direct-imported block components for SSR rendering
52
- * No React.lazy, no Suspense — HTML is fully visible without JS
53
- */
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ export type BlockImportFn = () => Promise<{ default: React.ComponentType<any> }>
52
+ export const BLOCK_IMPORTS_SSR: Record<string, BlockImportFn> = {}
53
+
54
+ /** @deprecated Use BLOCK_IMPORTS_SSR */
54
55
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
56
  export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {}
56
57
 
@@ -127,7 +128,6 @@ export const BLOCK_METADATA = {
127
128
  */
128
129
 
129
130
  import React from 'react'
130
- import dynamic from 'next/dynamic'
131
131
  import type { BlockConfig, BlockCategory } from '${convertCorePath('@/core/types', outputFilePath, config)}'
132
132
 
133
133
  ${fieldImports}
@@ -139,9 +139,8 @@ ${registryEntries}
139
139
  export const BLOCK_CATEGORIES: BlockCategory[] = [${categories.map(c => `'${c}'`).join(', ')}]
140
140
 
141
141
  /**
142
- * Lazy-loaded block components for runtime rendering
143
- * Each component is wrapped with React.lazy for code-splitting
144
- * Uses Object.values to get the first exported component (handles varying naming conventions)
142
+ * Lazy-loaded block components for client-side rendering (admin UI, block picker).
143
+ * Each component is wrapped with React.lazy for code-splitting.
145
144
  */
146
145
  export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<any>>> = {
147
146
  ${blocks.map(block => {
@@ -150,17 +149,32 @@ ${blocks.map(block => {
150
149
  }
151
150
 
152
151
  /**
153
- * Code-split block components for SSR rendering via next/dynamic.
154
- * Each block is a separate JS chunk — only blocks used on the page are loaded.
155
- * SSR is enabled (default) so HTML is fully visible without client JS.
152
+ * Server-side block import functions for RSC streaming.
153
+ *
154
+ * Each entry is an async import function NOT a resolved component.
155
+ * PageRenderer (async Server Component) calls these per-block with Suspense,
156
+ * enabling:
157
+ * - Per-block code splitting (only used blocks ship JS to client)
158
+ * - Streaming SSR (server flushes HTML progressively as blocks resolve)
159
+ * - Zero CLS (HTML is complete before client JS runs)
160
+ *
161
+ * For admin/client rendering, use BLOCK_COMPONENTS (React.lazy) instead.
156
162
  */
157
163
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
- export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {
164
+ export type BlockImportFn = () => Promise<{ default: React.ComponentType<any> }>
165
+ export const BLOCK_IMPORTS_SSR: Record<string, BlockImportFn> = {
159
166
  ${blocks.map(block => {
160
- return ` '${block.slug}': dynamic(() => import('${block.paths.component}'), { ssr: true })`
167
+ return ` '${block.slug}': () => import('${block.paths.component}')`
161
168
  }).join(',\n')}
162
169
  }
163
170
 
171
+ /**
172
+ * @deprecated Use BLOCK_IMPORTS_SSR with async PageRenderer instead.
173
+ * Kept for backwards compatibility with themes that override PageRenderer.
174
+ */
175
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
176
+ export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {}
177
+
164
178
  /**
165
179
  * Block registry metadata
166
180
  */