@nextsparkjs/core 0.1.0-beta.136 → 0.1.0-beta.137

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.
Files changed (60) hide show
  1. package/dist/lib/translations/registry.d.ts +5 -0
  2. package/dist/lib/translations/registry.d.ts.map +1 -1
  3. package/dist/lib/translations/registry.js +1 -0
  4. package/dist/providers/static-intl-provider.d.ts +9 -0
  5. package/dist/providers/static-intl-provider.d.ts.map +1 -0
  6. package/dist/providers/static-intl-provider.js +9 -0
  7. package/dist/styles/classes.json +1 -1
  8. package/dist/templates/app/(auth)/auth-error/page.tsx +0 -1
  9. package/dist/templates/app/(auth)/forgot-password/page.tsx +0 -1
  10. package/dist/templates/app/(auth)/layout.tsx +4 -1
  11. package/dist/templates/app/(auth)/login/page.tsx +0 -1
  12. package/dist/templates/app/(auth)/reset-password/page.tsx +0 -1
  13. package/dist/templates/app/(auth)/signup/page.tsx +10 -2
  14. package/dist/templates/app/(auth)/verify-email/page.tsx +0 -1
  15. package/dist/templates/app/(public)/[...slug]/page.tsx +14 -3
  16. package/dist/templates/app/(public)/layout.tsx +4 -1
  17. package/dist/templates/app/api/csp-report/route.ts +13 -9
  18. package/dist/templates/app/dashboard/(main)/[entity]/[id]/page.tsx +3 -21
  19. package/dist/templates/app/dashboard/(main)/[entity]/page.tsx +3 -22
  20. package/dist/templates/app/dashboard/settings/password/page.tsx +0 -1
  21. package/dist/templates/app/devtools/blocks/[slug]/page.tsx +3 -1
  22. package/dist/templates/app/devtools/blocks/page.tsx +10 -1
  23. package/dist/templates/app/devtools/config/page.tsx +10 -1
  24. package/dist/templates/app/devtools/features/page.tsx +10 -1
  25. package/dist/templates/app/devtools/flows/page.tsx +10 -1
  26. package/dist/templates/app/devtools/page.tsx +10 -1
  27. package/dist/templates/app/devtools/tags/page.tsx +10 -1
  28. package/dist/templates/app/devtools/tests/[[...path]]/page.tsx +10 -1
  29. package/dist/templates/app/layout.ppr.tsx +105 -0
  30. package/dist/templates/app/layout.tsx +10 -2
  31. package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +3 -5
  32. package/dist/templates/next.config.mjs +3 -0
  33. package/package.json +2 -2
  34. package/scripts/build/registry/__tests__/translation-registry-ppr.test.mjs +118 -0
  35. package/scripts/build/registry/generators/translation-registry.mjs +113 -2
  36. package/templates/app/(auth)/auth-error/page.tsx +0 -1
  37. package/templates/app/(auth)/forgot-password/page.tsx +0 -1
  38. package/templates/app/(auth)/layout.tsx +4 -1
  39. package/templates/app/(auth)/login/page.tsx +0 -1
  40. package/templates/app/(auth)/reset-password/page.tsx +0 -1
  41. package/templates/app/(auth)/signup/page.tsx +10 -2
  42. package/templates/app/(auth)/verify-email/page.tsx +0 -1
  43. package/templates/app/(public)/[...slug]/page.tsx +14 -3
  44. package/templates/app/(public)/layout.tsx +4 -1
  45. package/templates/app/api/csp-report/route.ts +13 -9
  46. package/templates/app/dashboard/(main)/[entity]/[id]/page.tsx +3 -21
  47. package/templates/app/dashboard/(main)/[entity]/page.tsx +3 -22
  48. package/templates/app/dashboard/settings/password/page.tsx +0 -1
  49. package/templates/app/devtools/blocks/[slug]/page.tsx +3 -1
  50. package/templates/app/devtools/blocks/page.tsx +10 -1
  51. package/templates/app/devtools/config/page.tsx +10 -1
  52. package/templates/app/devtools/features/page.tsx +10 -1
  53. package/templates/app/devtools/flows/page.tsx +10 -1
  54. package/templates/app/devtools/page.tsx +10 -1
  55. package/templates/app/devtools/tags/page.tsx +10 -1
  56. package/templates/app/devtools/tests/[[...path]]/page.tsx +10 -1
  57. package/templates/app/layout.ppr.tsx +105 -0
  58. package/templates/app/layout.tsx +10 -2
  59. package/templates/app/superadmin/docs/[section]/[page]/page.tsx +3 -5
  60. package/templates/next.config.mjs +3 -0
@@ -5,6 +5,11 @@
5
5
  * Integrates with next-intl and entity system for seamless i18n.
6
6
  */
7
7
  import type { SupportedLocale } from '../entities/types';
8
+ /**
9
+ * Deep merge with key preservation
10
+ * Priority: Core < Theme < Entity (later wins)
11
+ */
12
+ export declare function deepMergeMessages(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
8
13
  /**
9
14
  * Register theme messages for a locale
10
15
  * Call this from your project's initialization to provide theme-specific translations
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/translations/registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AA+FxD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7F;AAmDD;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA4DlC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/translations/registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAuBxD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyBzB;AAwCD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7F;AAmDD;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA4DlC"}
@@ -114,6 +114,7 @@ async function loadMergedTranslations(locale) {
114
114
  return mergedMessages;
115
115
  }
116
116
  export {
117
+ deepMergeMessages,
117
118
  loadMergedTranslations,
118
119
  registerThemeMessages
119
120
  };
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ interface StaticIntlProviderProps {
3
+ locale: string;
4
+ messages: Record<string, unknown>;
5
+ children: ReactNode;
6
+ }
7
+ export declare function StaticIntlProvider({ locale, messages, children }: StaticIntlProviderProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=static-intl-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-intl-provider.d.ts","sourceRoot":"","sources":["../../src/providers/static-intl-provider.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CAMzF"}
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { IntlProvider } from "use-intl/react";
4
+ function StaticIntlProvider({ locale, messages, children }) {
5
+ return /* @__PURE__ */ jsx(IntlProvider, { locale, messages, children });
6
+ }
7
+ export {
8
+ StaticIntlProvider
9
+ };
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-04-07T17:31:21.161Z",
2
+ "generated": "2026-04-08T19:30:03.335Z",
3
3
  "totalClasses": 1078,
4
4
  "classes": [
5
5
  "!text-2xl",
@@ -21,6 +21,5 @@ function AuthErrorPageWrapper() {
21
21
  )
22
22
  }
23
23
 
24
- export const dynamic = 'force-dynamic'
25
24
 
26
25
  export default getTemplateOrDefault('app/(auth)/auth-error/page.tsx', AuthErrorPageWrapper)
@@ -211,6 +211,5 @@ function ForgotPasswordPage() {
211
211
  }
212
212
 
213
213
  // Opt out of static generation due to client-side state
214
- export const dynamic = 'force-dynamic';
215
214
 
216
215
  export default getTemplateOrDefaultClient('app/(auth)/forgot-password/page.tsx', ForgotPasswordPage)
@@ -1,3 +1,4 @@
1
+ import { Suspense } from 'react'
1
2
  import type { Metadata } from 'next'
2
3
  import { AuthWrapper } from '@nextsparkjs/core/components/auth/layouts/AuthWrapper'
3
4
  import { getTemplateOrDefault, getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
@@ -34,7 +35,9 @@ function AuthLayout({ children }: { children: React.ReactNode }) {
34
35
  </div>
35
36
 
36
37
  <AuthWrapper>
37
- {children}
38
+ <Suspense fallback={null}>
39
+ {children}
40
+ </Suspense>
38
41
  </AuthWrapper>
39
42
  </div>
40
43
 
@@ -16,6 +16,5 @@ function LoginPage() {
16
16
  return <LoginForm />
17
17
  }
18
18
 
19
- export const dynamic = 'force-dynamic'
20
19
 
21
20
  export default getTemplateOrDefault('app/(auth)/login/page.tsx', LoginPage)
@@ -207,6 +207,5 @@ function ResetPasswordPage() {
207
207
  }
208
208
 
209
209
  // Opt out of static generation due to client-side state
210
- export const dynamic = 'force-dynamic';
211
210
 
212
211
  export default getTemplateOrDefaultClient('app/(auth)/reset-password/page.tsx', ResetPasswordPage)
@@ -1,3 +1,4 @@
1
+ import { Suspense } from 'react'
1
2
  import type { Metadata } from 'next'
2
3
  import { redirect } from 'next/navigation'
3
4
  import { SignupForm } from '@nextsparkjs/core/components/auth/forms/SignupForm'
@@ -15,7 +16,7 @@ export const metadata: Metadata = getMetadataOrDefault(
15
16
  defaultMetadata
16
17
  )
17
18
 
18
- async function SignupPage() {
19
+ async function SignupPageContent() {
19
20
  const registrationMode = AUTH_CONFIG?.registration?.mode ?? 'open'
20
21
 
21
22
  // In invitation-only mode, allow the first user to register
@@ -38,6 +39,13 @@ async function SignupPage() {
38
39
  return <SignupForm />
39
40
  }
40
41
 
41
- export const dynamic = 'force-dynamic'
42
+ function SignupPage() {
43
+ return (
44
+ <Suspense fallback={null}>
45
+ <SignupPageContent />
46
+ </Suspense>
47
+ )
48
+ }
49
+
42
50
 
43
51
  export default getTemplateOrDefault('app/(auth)/signup/page.tsx', SignupPage)
@@ -185,6 +185,5 @@ function VerifyEmailPage() {
185
185
  );
186
186
  }
187
187
 
188
- export const dynamic = 'force-dynamic';
189
188
 
190
189
  export default getTemplateOrDefaultClient('app/(auth)/verify-email/page.tsx', VerifyEmailPage)
@@ -13,6 +13,7 @@
13
13
  * 2. Falls back to default PageRenderer if no template
14
14
  */
15
15
 
16
+ import { Suspense } from 'react'
16
17
  import { notFound } from 'next/navigation'
17
18
  import { query } from '@nextsparkjs/core/lib/db'
18
19
  import { PageRenderer } from '@nextsparkjs/core/components/public/pageBuilder'
@@ -51,7 +52,6 @@ function getEntityConfigs(): Record<string, EntityConfig> {
51
52
  }
52
53
 
53
54
  // Enable ISR with 1 hour revalidation
54
- export const revalidate = 3600
55
55
 
56
56
  interface PageProps {
57
57
  params: Promise<{ slug: string[] }>
@@ -247,9 +247,9 @@ export async function generateMetadata({
247
247
  }
248
248
 
249
249
  /**
250
- * Main catch-all page component
250
+ * Main catch-all page content (async, wrapped in Suspense by parent)
251
251
  */
252
- export default async function DynamicPublicPage({
252
+ async function DynamicPublicPageContent({
253
253
  params,
254
254
  searchParams,
255
255
  }: PageProps) {
@@ -376,3 +376,14 @@ export default async function DynamicPublicPage({
376
376
  // No match found
377
377
  notFound()
378
378
  }
379
+
380
+ /**
381
+ * Main catch-all page component — wraps async content in Suspense for PPR compatibility
382
+ */
383
+ export default function DynamicPublicPage(props: PageProps) {
384
+ return (
385
+ <Suspense fallback={null}>
386
+ <DynamicPublicPageContent {...props} />
387
+ </Suspense>
388
+ )
389
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react"
1
2
  import type { Metadata } from "next"
2
3
  import { PublicNavbar } from '@nextsparkjs/core/components/app/layouts/PublicNavbar'
3
4
  import { PublicFooter } from '@nextsparkjs/core/components/app/layouts/PublicFooter'
@@ -29,7 +30,9 @@ function PublicLayout({
29
30
 
30
31
  {/* Main Content */}
31
32
  <main className="flex-1">
32
- {children}
33
+ <Suspense fallback={null}>
34
+ {children}
35
+ </Suspense>
33
36
  </main>
34
37
 
35
38
  {/* Public Footer */}
@@ -5,15 +5,18 @@ import { NextRequest, NextResponse } from 'next/server';
5
5
  let checkDistributedRateLimit: ((id: string, tier: string) => Promise<{ allowed: boolean; limit: number; remaining: number; resetTime: number; retryAfter?: number }>) | null = null;
6
6
  let createRateLimitErrorResponse: ((result: { allowed: boolean; limit: number; remaining: number; resetTime: number; retryAfter?: number }) => NextResponse) | null = null;
7
7
 
8
- // Try to load rate limiting functions
9
- try {
10
- // eslint-disable-next-line @typescript-eslint/no-require-imports
11
- const rateLimitModule = require('@nextsparkjs/core/lib/api');
12
- checkDistributedRateLimit = rateLimitModule.checkDistributedRateLimit;
13
- createRateLimitErrorResponse = rateLimitModule.createRateLimitErrorResponse;
14
- } catch {
15
- // Rate limiting not available - will skip rate limit checks
16
- console.warn('[CSP Report] Rate limiting not available - running without rate limits');
8
+ // Lazy-load rate limiting functions on first request
9
+ let rateLimitLoaded = false;
10
+ async function ensureRateLimitLoaded() {
11
+ if (rateLimitLoaded) return;
12
+ rateLimitLoaded = true;
13
+ try {
14
+ const rateLimitModule = await import('@nextsparkjs/core/lib/api');
15
+ checkDistributedRateLimit = rateLimitModule.checkDistributedRateLimit;
16
+ createRateLimitErrorResponse = rateLimitModule.createRateLimitErrorResponse;
17
+ } catch {
18
+ console.warn('[CSP Report] Rate limiting not available - running without rate limits');
19
+ }
17
20
  }
18
21
 
19
22
  /**
@@ -73,6 +76,7 @@ function getClientIp(request: NextRequest): string {
73
76
  }
74
77
 
75
78
  export async function POST(request: NextRequest) {
79
+ await ensureRateLimitLoaded();
76
80
  const requestId = randomUUID().slice(0, 8);
77
81
  let rateLimitHeaders: Record<string, string> = {};
78
82
 
@@ -77,27 +77,9 @@ async function EntityDetailPage({ params }: PageProps) {
77
77
  )
78
78
  }
79
79
 
80
- export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
81
- const resolvedParams = await params
82
- const entitySlug = resolvedParams.entity
83
-
84
- if (!(entitySlug in getEntityRegistry())) {
85
- return {
86
- title: 'Not Found - Dashboard'
87
- }
88
- }
89
-
90
- const entityConfig = getEntity(entitySlug as string)
91
- if (!entityConfig || !isEntityConfig(entityConfig)) {
92
- return {
93
- title: 'Not Found - Dashboard'
94
- }
95
- }
96
-
97
- return {
98
- title: `${entityConfig.names.plural} #${resolvedParams.id} - Dashboard`,
99
- description: `View details for ${entityConfig.names.singular}`
100
- }
80
+ export const metadata: Metadata = {
81
+ title: 'Dashboard',
82
+ description: 'View entity details'
101
83
  }
102
84
 
103
85
  export default EntityDetailPage
@@ -63,28 +63,9 @@ async function EntityListPage({ params }: PageProps) {
63
63
  )
64
64
  }
65
65
 
66
- export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
67
- const resolvedParams = await params
68
- const entitySlug = resolvedParams.entity
69
-
70
- const registry = getEntityRegistry()
71
- if (!(entitySlug in registry)) {
72
- return {
73
- title: 'Not Found - Dashboard'
74
- }
75
- }
76
-
77
- const entityConfig = getEntity(entitySlug)
78
- if (!entityConfig || !isEntityConfig(entityConfig)) {
79
- return {
80
- title: 'Not Found - Dashboard'
81
- }
82
- }
83
-
84
- return {
85
- title: `${entityConfig.names.plural} - Dashboard`,
86
- description: `Manage ${entityConfig.names.plural.toLowerCase()} in your dashboard`
87
- }
66
+ export const metadata: Metadata = {
67
+ title: 'Dashboard',
68
+ description: 'Manage entities in your dashboard'
88
69
  }
89
70
 
90
71
  export default getTemplateOrDefault('app/dashboard/(main)/[entity]/page.tsx', EntityListPage)
@@ -301,6 +301,5 @@ function UpdatePasswordPage() {
301
301
  }
302
302
 
303
303
  // Opt out of static generation due to client-side state
304
- export const dynamic = 'force-dynamic';
305
304
 
306
305
  export default getTemplateOrDefaultClient('app/dashboard/settings/password/page.tsx', UpdatePasswordPage)
@@ -33,7 +33,9 @@ export default async function BlockDetailPage({ params }: BlockDetailPageProps)
33
33
  * Generate static params for all blocks
34
34
  */
35
35
  export async function generateStaticParams() {
36
- return Object.keys(BLOCK_REGISTRY).map((slug) => ({
36
+ const keys = Object.keys(BLOCK_REGISTRY);
37
+ if (keys.length === 0) return [{ slug: '_placeholder' }];
38
+ return keys.map((slug) => ({
37
39
  slug,
38
40
  }));
39
41
  }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { BlocksViewer } from "@nextsparkjs/core/components/devtools/BlocksViewer";
2
3
  import { LayoutGrid } from "lucide-react";
3
4
  import { getTranslations } from "next-intl/server";
@@ -8,7 +9,7 @@ import { getTranslations } from "next-intl/server";
8
9
  * Displays all page builder blocks with field definitions and coverage info.
9
10
  * Provides filtering and search capabilities.
10
11
  */
11
- export default async function DevBlocksPage() {
12
+ async function DevBlocksPageContent() {
12
13
  const t = await getTranslations("devtools.blocks");
13
14
 
14
15
  return (
@@ -29,3 +30,11 @@ export default async function DevBlocksPage() {
29
30
  </div>
30
31
  );
31
32
  }
33
+
34
+ export default function DevBlocksPage() {
35
+ return (
36
+ <Suspense fallback={null}>
37
+ <DevBlocksPageContent />
38
+ </Suspense>
39
+ );
40
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { ConfigViewer } from "@nextsparkjs/core/components/devtools";
2
3
  import { Settings } from "lucide-react";
3
4
  import { getTranslations } from "next-intl/server";
@@ -8,7 +9,7 @@ import { getTranslations } from "next-intl/server";
8
9
  * Displays theme configuration and entity registry information.
9
10
  * Provides read-only view with JSON formatting and copy functionality.
10
11
  */
11
- export default async function DevConfigPage() {
12
+ async function DevConfigPageContent() {
12
13
  const t = await getTranslations('dev.config');
13
14
 
14
15
  return (
@@ -29,3 +30,11 @@ export default async function DevConfigPage() {
29
30
  </div>
30
31
  );
31
32
  }
33
+
34
+ export default function DevConfigPage() {
35
+ return (
36
+ <Suspense fallback={null}>
37
+ <DevConfigPageContent />
38
+ </Suspense>
39
+ );
40
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { FeaturesViewer } from "@nextsparkjs/core/components/devtools/FeaturesViewer";
2
3
  import { Layers } from "lucide-react";
3
4
  import { getTranslations } from "next-intl/server";
@@ -8,7 +9,7 @@ import { getTranslations } from "next-intl/server";
8
9
  * Displays all features from the testing registry with coverage information.
9
10
  * Provides filtering and search capabilities.
10
11
  */
11
- export default async function DevFeaturesPage() {
12
+ async function DevFeaturesPageContent() {
12
13
  const t = await getTranslations("devtools.features");
13
14
 
14
15
  return (
@@ -29,3 +30,11 @@ export default async function DevFeaturesPage() {
29
30
  </div>
30
31
  );
31
32
  }
33
+
34
+ export default function DevFeaturesPage() {
35
+ return (
36
+ <Suspense fallback={null}>
37
+ <DevFeaturesPageContent />
38
+ </Suspense>
39
+ );
40
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { FlowsViewer } from "@nextsparkjs/core/components/devtools/FlowsViewer";
2
3
  import { GitBranch } from "lucide-react";
3
4
  import { getTranslations } from "next-intl/server";
@@ -8,7 +9,7 @@ import { getTranslations } from "next-intl/server";
8
9
  * Displays all user journey flows from the testing registry with coverage information.
9
10
  * Provides filtering and search capabilities.
10
11
  */
11
- export default async function DevFlowsPage() {
12
+ async function DevFlowsPageContent() {
12
13
  const t = await getTranslations("devtools.flows");
13
14
 
14
15
  return (
@@ -29,3 +30,11 @@ export default async function DevFlowsPage() {
29
30
  </div>
30
31
  );
31
32
  }
33
+
34
+ export default function DevFlowsPage() {
35
+ return (
36
+ <Suspense fallback={null}>
37
+ <DevFlowsPageContent />
38
+ </Suspense>
39
+ );
40
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { Code, Palette, FileText, Settings } from "lucide-react";
2
3
  import Link from "next/link";
3
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@nextsparkjs/core/components/ui/card";
@@ -9,7 +10,7 @@ import { getTranslations } from "next-intl/server";
9
10
  * Dashboard with quick links to development tools and documentation.
10
11
  * Shows overview of available sections in the dev area.
11
12
  */
12
- export default async function DevHomePage() {
13
+ async function DevHomePageContent() {
13
14
  const t = await getTranslations('dev');
14
15
  const tCommon = await getTranslations('common');
15
16
 
@@ -119,3 +120,11 @@ export default async function DevHomePage() {
119
120
  </div>
120
121
  );
121
122
  }
123
+
124
+ export default function DevHomePage() {
125
+ return (
126
+ <Suspense fallback={null}>
127
+ <DevHomePageContent />
128
+ </Suspense>
129
+ );
130
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { TagsOverview } from "@nextsparkjs/core/components/devtools/TagsOverview";
2
3
  import { Tag } from "lucide-react";
3
4
  import { getTranslations } from "next-intl/server";
@@ -8,7 +9,7 @@ import { getTranslations } from "next-intl/server";
8
9
  * Displays all test tags organized by category.
9
10
  * Provides search and copy-to-clipboard functionality.
10
11
  */
11
- export default async function DevTagsPage() {
12
+ async function DevTagsPageContent() {
12
13
  const t = await getTranslations("devtools.tags");
13
14
 
14
15
  return (
@@ -29,3 +30,11 @@ export default async function DevTagsPage() {
29
30
  </div>
30
31
  );
31
32
  }
33
+
34
+ export default function DevTagsPage() {
35
+ return (
36
+ <Suspense fallback={null}>
37
+ <DevTagsPageContent />
38
+ </Suspense>
39
+ );
40
+ }
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { TestCasesViewer } from "@nextsparkjs/core/components/devtools";
2
3
  import { FileText } from "lucide-react";
3
4
  import { getTranslations } from "next-intl/server";
@@ -17,7 +18,7 @@ interface DevTestsPageProps {
17
18
  * - /dev/tests/auth/login-logout.bdd.md → File selected
18
19
  * - /dev/tests/page-builder/admin/block-editor.bdd.md → Nested file
19
20
  */
20
- export default async function DevTestsPage({ params }: DevTestsPageProps) {
21
+ async function DevTestsPageContent({ params }: DevTestsPageProps) {
21
22
  const { path } = await params;
22
23
  const t = await getTranslations("dev.tests");
23
24
 
@@ -45,3 +46,11 @@ export default async function DevTestsPage({ params }: DevTestsPageProps) {
45
46
  </div>
46
47
  );
47
48
  }
49
+
50
+ export default function DevTestsPage({ params }: DevTestsPageProps) {
51
+ return (
52
+ <Suspense fallback={null}>
53
+ <DevTestsPageContent params={params} />
54
+ </Suspense>
55
+ );
56
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Root Layout — PPR-compatible (Partial Prerendering)
3
+ *
4
+ * Fully static: no async data fetching in the root layout.
5
+ * The PPR static shell (html/body/fonts/content) renders instantly from CDN.
6
+ *
7
+ * Prerequisites:
8
+ * - Next.js 16.2.2+ with cacheComponents: true in next.config
9
+ * - Run `pnpm build:registries` to generate PPR exports
10
+ * - See docs/migration-ppr.md for full migration guide
11
+ *
12
+ * Key differences from the default async layout:
13
+ * - Layout is sync (not async) — enables PPR static shell
14
+ * - Uses StaticIntlProvider instead of NextIntlClientProvider
15
+ * - Messages/locale/theme mode are pre-merged at build time via registry
16
+ * - No PluginService.initializeAll() — plugins initialize on-demand
17
+ *
18
+ * Generated by @nextspark/core
19
+ * Regenerate with: npx nextspark generate
20
+ */
21
+
22
+ import type { Metadata } from "next"
23
+ import { Suspense } from "react"
24
+ import { Geist, Geist_Mono } from "next/font/google"
25
+
26
+ import "./globals.css"
27
+ import { getBillingResourceHints } from "@nextsparkjs/core/lib/billing/gateways/factory"
28
+ import { StaticIntlProvider } from "@nextsparkjs/core/providers/static-intl-provider"
29
+ import { QueryProvider } from "@nextsparkjs/core/providers/query-provider"
30
+ import { ThemeProvider as NextThemeProvider } from "@nextsparkjs/core/providers/theme-provider"
31
+ import { ThemeProvider as CustomThemeProvider } from "@nextsparkjs/core/lib/theme/ThemeProvider"
32
+ import { TeamProvider } from "@nextsparkjs/core/contexts/TeamContext"
33
+ import { SubscriptionProvider } from "@nextsparkjs/core/contexts/SubscriptionContext"
34
+ import { Toaster } from "@nextsparkjs/core/components/ui/sonner"
35
+ import { getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
36
+ import { DEFAULT_LOCALE, DEFAULT_THEME_MODE, STATIC_MESSAGES } from '@nextsparkjs/registries/translation-registry'
37
+
38
+ const geistSans = Geist({
39
+ variable: "--font-geist-sans",
40
+ subsets: ["latin"],
41
+ })
42
+
43
+ const geistMono = Geist_Mono({
44
+ variable: "--font-geist-mono",
45
+ subsets: ["latin"],
46
+ })
47
+
48
+ const defaultMetadata: Metadata = {
49
+ title: "NextSpark App",
50
+ description: "Built with NextSpark",
51
+ }
52
+
53
+ export const metadata: Metadata = getMetadataOrDefault(
54
+ 'app/layout.tsx',
55
+ defaultMetadata
56
+ )
57
+
58
+ export default function RootLayout({
59
+ children,
60
+ }: Readonly<{
61
+ children: React.ReactNode
62
+ }>) {
63
+ return (
64
+ <html lang={DEFAULT_LOCALE} suppressHydrationWarning>
65
+ <head>
66
+ {(() => {
67
+ const hints = getBillingResourceHints()
68
+ return (
69
+ <>
70
+ {hints.preconnect.map((domain) => (
71
+ <link key={`pre-${domain}`} rel="preconnect" href={domain} />
72
+ ))}
73
+ {[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
74
+ <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
75
+ ))}
76
+ </>
77
+ )
78
+ })()}
79
+ </head>
80
+ <body
81
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
82
+ >
83
+ <StaticIntlProvider locale={DEFAULT_LOCALE} messages={STATIC_MESSAGES}>
84
+ <NextThemeProvider
85
+ attribute="class"
86
+ defaultTheme={DEFAULT_THEME_MODE}
87
+ enableSystem={DEFAULT_THEME_MODE === 'system'}
88
+ disableTransitionOnChange
89
+ >
90
+ <CustomThemeProvider>
91
+ <QueryProvider>
92
+ <TeamProvider>
93
+ <SubscriptionProvider>
94
+ <main>{children}</main>
95
+ <Suspense><Toaster position="bottom-left" /></Suspense>
96
+ </SubscriptionProvider>
97
+ </TeamProvider>
98
+ </QueryProvider>
99
+ </CustomThemeProvider>
100
+ </NextThemeProvider>
101
+ </StaticIntlProvider>
102
+ </body>
103
+ </html>
104
+ )
105
+ }
@@ -1,10 +1,18 @@
1
1
  /**
2
- * AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
2
+ * Root Layout Async (default)
3
+ *
4
+ * Uses async data fetching for locale, messages, and theme mode.
5
+ * Compatible with all Next.js versions (15+, 16+).
6
+ *
7
+ * For PPR-optimized layout (requires Next.js 16.2.2+ with cacheComponents: true),
8
+ * see layout.ppr.tsx which uses pre-merged static imports for faster shell rendering.
9
+ *
3
10
  * Generated by @nextspark/core
4
11
  * Regenerate with: npx nextspark generate
5
12
  */
6
13
 
7
14
  import type { Metadata } from "next"
15
+ import { Suspense } from "react"
8
16
  import { Geist, Geist_Mono } from "next/font/google"
9
17
  import { NextIntlClientProvider } from 'next-intl'
10
18
  import { getMessages } from 'next-intl/server'
@@ -92,7 +100,7 @@ export default async function RootLayout({
92
100
  <SubscriptionProvider>
93
101
  <TranslationContextManager />
94
102
  <main>{children}</main>
95
- <Toaster position="bottom-left" />
103
+ <Suspense><Toaster position="bottom-left" /></Suspense>
96
104
  </SubscriptionProvider>
97
105
  </TeamProvider>
98
106
  </QueryProvider>