@dheme/next 1.11.0 → 1.13.0

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
@@ -34,19 +34,15 @@ pnpm add @dheme/next @dheme/react @dheme/sdk
34
34
 
35
35
  ```tsx
36
36
  // app/layout.tsx (Server Component)
37
- import { DhemeScript } from '@dheme/next/server'; // Server Component — server path only
38
- import { DhemeProvider } from '@dheme/next';
37
+ import { DhemeSetup } from '@dheme/next';
39
38
 
40
39
  export default function RootLayout({ children }: { children: React.ReactNode }) {
41
40
  return (
42
- <html lang="en">
43
- <head>
44
- <DhemeScript apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" />
45
- </head>
41
+ <html lang="en" suppressHydrationWarning>
46
42
  <body>
47
- <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
43
+ <DhemeSetup apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" defaultMode="light">
48
44
  {children}
49
- </DhemeProvider>
45
+ </DhemeSetup>
50
46
  </body>
51
47
  </html>
52
48
  );
@@ -55,6 +51,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
55
51
 
56
52
  That's it. Your app has 19 CSS variables applied server-side — zero client-side fetch, zero FOUC.
57
53
 
54
+ `DhemeSetup` combines `DhemeScript` + `DhemeProvider` in a single declaration and ensures `defaultMode` is always consistent between them.
55
+
58
56
  ## How It Works
59
57
 
60
58
  ### Every visit (zero FOUC, zero client fetch)
@@ -74,25 +72,80 @@ The server maintains an **in-memory LRU cache** (100 entries, 1h TTL). Since the
74
72
 
75
73
  ## Components
76
74
 
75
+ ### `<DhemeSetup>` (Server Component — recommended)
76
+
77
+ Single entry point that combines `DhemeScript` + `DhemeProvider`. Use this when you don't need client-side callbacks (`onThemeChange`, `onModeChange`, etc.).
78
+
79
+ ```tsx
80
+ // app/layout.tsx
81
+ import { DhemeSetup } from '@dheme/next';
82
+
83
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
84
+ return (
85
+ <html lang="en" suppressHydrationWarning>
86
+ <body>
87
+ <DhemeSetup
88
+ apiKey={process.env.DHEME_API_KEY!}
89
+ theme="#3b82f6"
90
+ defaultMode="dark" // Specified once — propagated to both script and provider
91
+ themeParams={{ radius: 0.75, tailwindVersion: 'v4' }}
92
+ >
93
+ {children}
94
+ </DhemeSetup>
95
+ </body>
96
+ </html>
97
+ );
98
+ }
99
+ ```
100
+
101
+ | Prop | Type | Default | Description |
102
+ | ----------------- | ------------------------------------------------- | --------- | ---------------------------------------------------------------- |
103
+ | `theme` | `string` | - | **Required.** Primary HEX color. |
104
+ | `defaultMode` | `'light' \| 'dark'` | `'light'` | Initial color mode. Passed to both script and provider. |
105
+ | `themeParams` | `Omit<GenerateThemeRequest, 'theme'>` | - | Additional generation parameters. |
106
+ | `apiKey` | `string` | - | Dheme API key. Server-side only — never sent to the browser. |
107
+ | `baseUrl` | `string` | - | Override API base URL. |
108
+ | `nonce` | `string` | - | CSP nonce for injected style/script tags. |
109
+ | `onGenerateTheme` | `(params) => Promise<GenerateThemeResponse>` | - | Server-side custom theme function. Only used by `DhemeScript`. |
110
+ | `proxyUrl` | `string` | `'/api/dheme'` | Client-side proxy route URL. |
111
+ | `cookieSync` | `boolean` | `true` | Sync mode and params to cookies. |
112
+ | `persist` | `boolean` | `true` | Persist theme in localStorage. |
113
+ | `autoApply` | `boolean` | `true` | Apply CSS variables automatically. |
114
+ | `children` | `React.ReactNode` | - | **Required.** App content. |
115
+
116
+ > For apps that need `onThemeChange`, `onModeChange`, `onError`, or `loadingContent`, use `DhemeScript` + `DhemeProvider` separately (see below).
117
+
118
+ ---
119
+
77
120
  ### `<DhemeScript>` (Server Component)
78
121
 
79
- Fetches the theme on the server and renders inline `<style>` + `<script>` tags. Place it in `<head>`.
122
+ Fetches the theme on the server and renders inline `<style>` + `<script>` tags. Used directly when you need to place the styles in `<head>` or when combining with a custom `DhemeProvider`.
80
123
 
81
124
  ```tsx
82
- <DhemeScript
83
- apiKey={process.env.DHEME_API_KEY!} // Required — server-side only
84
- theme="#3b82f6" // Required — primary HEX color
85
- themeParams={{
86
- // Optional generation params
87
- radius: 0.75,
88
- saturationAdjust: 10,
89
- borderIsColored: false,
90
- tailwindVersion: 'v4', // 'v3' | 'v4' (default: 'v4')
91
- }}
92
- defaultMode="light" // 'light' | 'dark' (default: 'light')
93
- baseUrl="http://localhost:3005" // Override API URL (optional)
94
- nonce="abc123" // CSP nonce (optional)
95
- />
125
+ import { DhemeScript, DhemeProvider } from '@dheme/next';
126
+
127
+ // app/layout.tsx
128
+ export default function RootLayout({ children }) {
129
+ return (
130
+ <html lang="en" suppressHydrationWarning>
131
+ <head>
132
+ <DhemeScript
133
+ apiKey={process.env.DHEME_API_KEY!}
134
+ theme="#3b82f6"
135
+ defaultMode="dark"
136
+ themeParams={{ radius: 0.75, tailwindVersion: 'v4' }}
137
+ nonce="abc123"
138
+ />
139
+ </head>
140
+ <body>
141
+ <DhemeProvider theme="#3b82f6" defaultMode="dark">
142
+ {children}
143
+ </DhemeProvider>
144
+ </body>
145
+ </html>
146
+ );
147
+ }
148
+ ```
96
149
  ```
97
150
 
98
151
  | Prop | Type | Default | Description |
@@ -127,20 +180,16 @@ A floating FAB for real-time theme generation. Re-exported from `@dheme/react` w
127
180
 
128
181
  ```tsx
129
182
  // app/layout.tsx
130
- import { DhemeScript } from '@dheme/next/server';
131
- import { DhemeProvider, ThemeGenerator } from '@dheme/next';
183
+ import { DhemeSetup, ThemeGenerator } from '@dheme/next';
132
184
 
133
185
  export default function RootLayout({ children }: { children: React.ReactNode }) {
134
186
  return (
135
187
  <html lang="en" suppressHydrationWarning>
136
- <head>
137
- <DhemeScript apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" />
138
- </head>
139
188
  <body>
140
- <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
189
+ <DhemeSetup apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
141
190
  {children}
142
191
  <ThemeGenerator />
143
- </DhemeProvider>
192
+ </DhemeSetup>
144
193
  </body>
145
194
  </html>
146
195
  );
@@ -303,23 +352,20 @@ DHEME_BASE_URL=http://localhost:3005
303
352
  ### `app/layout.tsx`
304
353
 
305
354
  ```tsx
306
- import { DhemeScript } from '@dheme/next/server';
307
- import { DhemeProvider } from '@dheme/next';
355
+ import { DhemeSetup } from '@dheme/next';
308
356
 
309
357
  export default function RootLayout({ children }: { children: React.ReactNode }) {
310
358
  return (
311
359
  <html lang="en" suppressHydrationWarning>
312
- <head>
313
- <DhemeScript
360
+ <body>
361
+ <DhemeSetup
314
362
  apiKey={process.env.DHEME_API_KEY!}
315
363
  theme="#3b82f6"
364
+ defaultMode="light"
316
365
  themeParams={{ radius: 0.5 }}
317
- />
318
- </head>
319
- <body>
320
- <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
366
+ >
321
367
  {children}
322
- </DhemeProvider>
368
+ </DhemeSetup>
323
369
  </body>
324
370
  </html>
325
371
  );
@@ -386,10 +432,14 @@ export async function GET(request: Request) {
386
432
  Server Client
387
433
  ────── ──────
388
434
 
435
+ DhemeSetup (Server Component)
436
+
437
+ ├──────────────────────────────────────────────┐
438
+ │ │
389
439
  Request → DhemeScript (Server Component) DhemeProvider (Client Component)
390
440
  │ │
391
441
  ├─ themeCache.get(key) ├─ localStorage cache check
392
- │ ↓ miss? call SDK │ ↓ hit? serve instantly
442
+ │ ↓ miss? call SDK │ ↓ hit? apply synchronously
393
443
  ├─ themeCache.set(key, data) ├─ background revalidation
394
444
  │ │
395
445
  ├─ <style> with :root + .dark ├─ cookie sync (mode + params)
@@ -414,13 +464,16 @@ Request → DhemeScript (Server Component) DhemeProvider (Client Component)
414
464
 
415
465
  ## TypeScript
416
466
 
417
- All types are exported:
467
+ All types are exported from `@dheme/next`:
418
468
 
419
469
  ```typescript
420
- // Client types
421
470
  import type {
471
+ // Component props
472
+ DhemeSetupProps,
422
473
  DhemeProviderProps,
474
+ DhemeScriptProps,
423
475
  ThemeGeneratorProps,
476
+ // Theme types
424
477
  ThemeMode,
425
478
  ThemeDataState,
426
479
  ThemeActionsState,
@@ -430,8 +483,8 @@ import type {
430
483
  HSLColor,
431
484
  } from '@dheme/next';
432
485
 
433
- // Server types
434
- import type { DhemeScriptProps, GenerateThemeStylesOptions } from '@dheme/next/server';
486
+ // Server utility types
487
+ import type { GenerateThemeStylesOptions } from '@dheme/next/server';
435
488
  ```
436
489
 
437
490
  ## Related Packages
package/dist/index.d.mts CHANGED
@@ -10,17 +10,80 @@ interface DhemeProviderProps extends DhemeProviderProps$1 {
10
10
  * URL of a proxy route that forwards theme requests server-side,
11
11
  * keeping the API key out of the browser entirely.
12
12
  *
13
+ * Defaults to `"/api/dheme"` when neither `apiKey` nor `onGenerateTheme` is provided.
14
+ * This means the only required setup is creating the route — no need to pass this prop explicitly.
15
+ *
13
16
  * Set up the route with `createDhemeHandler` from `@dheme/next/server`:
14
17
  * @example
15
18
  * // app/api/dheme/route.ts
16
19
  * import { createDhemeHandler } from '@dheme/next/server';
17
20
  * export const { POST } = createDhemeHandler({ apiKey: process.env.DHEME_API_KEY! });
18
21
  *
19
- * // layout.tsx
20
- * <DhemeProvider proxyUrl="/api/dheme" theme="..." />
22
+ * // layout.tsx — proxyUrl is optional, defaults to "/api/dheme"
23
+ * <DhemeProvider theme="..." themeParams={...} />
21
24
  */
22
25
  proxyUrl?: string;
23
26
  }
27
+ interface DhemeScriptProps {
28
+ /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
29
+ apiKey?: string;
30
+ theme: string;
31
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
32
+ defaultMode?: ThemeMode;
33
+ baseUrl?: string;
34
+ nonce?: string;
35
+ /**
36
+ * Custom theme generation function. When provided, replaces the SDK client's
37
+ * generateTheme call entirely. Useful for internal use cases with custom endpoints.
38
+ *
39
+ * @example
40
+ * // Call an internal proxy route without API key:
41
+ * onGenerateTheme={async (params) => {
42
+ * const res = await fetch('/api/generate-theme/proxy', {
43
+ * method: 'POST',
44
+ * headers: { 'Content-Type': 'application/json' },
45
+ * body: JSON.stringify(params),
46
+ * });
47
+ * return res.json();
48
+ * }}
49
+ */
50
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
51
+ }
52
+ interface DhemeSetupProps {
53
+ /** Primary color hex for the theme (e.g. "7C3AED" or "#7C3AED") */
54
+ theme: string;
55
+ /**
56
+ * Default color mode. Passed to both DhemeScript and DhemeProvider — single source of truth.
57
+ * @default 'light'
58
+ */
59
+ defaultMode?: ThemeMode;
60
+ /** Additional theme generation parameters */
61
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
62
+ /** API key for the Dheme service. Server-side only — never sent to the browser. */
63
+ apiKey?: string;
64
+ /** Override the base URL for the Dheme API */
65
+ baseUrl?: string;
66
+ /** Nonce for Content Security Policy */
67
+ nonce?: string;
68
+ /**
69
+ * Server-side custom theme generation function.
70
+ * Replaces the SDK client call in DhemeScript. NOT forwarded to DhemeProvider.
71
+ * For client-side custom generation, use DhemeProvider directly with onGenerateTheme.
72
+ */
73
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
74
+ /**
75
+ * URL of the client-side proxy route.
76
+ * Defaults to "/api/dheme" when no apiKey is provided.
77
+ */
78
+ proxyUrl?: string;
79
+ /** Sync active mode and theme params to cookies for SSR hydration. @default true */
80
+ cookieSync?: boolean;
81
+ /** Persist theme data in localStorage for instant cache hits. @default true */
82
+ persist?: boolean;
83
+ /** Automatically apply CSS variables when theme changes. @default true */
84
+ autoApply?: boolean;
85
+ children: React.ReactNode;
86
+ }
24
87
  interface GenerateThemeStylesOptions {
25
88
  /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
26
89
  apiKey?: string;
@@ -37,4 +100,31 @@ interface GenerateThemeStylesOptions {
37
100
 
38
101
  declare function DhemeProvider({ children, cookieSync, proxyUrl, onThemeChange, onModeChange, onGenerateTheme, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
39
102
 
40
- export { DhemeProvider, type DhemeProviderProps, type GenerateThemeStylesOptions };
103
+ declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme, }: DhemeScriptProps): Promise<React.ReactElement>;
104
+
105
+ /**
106
+ * Combined Server Component that renders DhemeScript + DhemeProvider in a single declaration.
107
+ * Accepts `defaultMode` once, eliminating the need to keep it in sync across two components.
108
+ *
109
+ * @example
110
+ * // app/layout.tsx
111
+ * import { DhemeSetup } from '@dheme/next';
112
+ *
113
+ * export default function RootLayout({ children }: { children: React.ReactNode }) {
114
+ * return (
115
+ * <html>
116
+ * <body>
117
+ * <DhemeSetup theme="7C3AED" defaultMode="dark" apiKey={process.env.DHEME_API_KEY}>
118
+ * {children}
119
+ * </DhemeSetup>
120
+ * </body>
121
+ * </html>
122
+ * );
123
+ * }
124
+ *
125
+ * For apps that need client-side callbacks (onThemeChange, onModeChange, onError, loadingContent),
126
+ * use DhemeScript + DhemeProvider separately.
127
+ */
128
+ declare function DhemeSetup({ theme, defaultMode, themeParams, apiKey, baseUrl, nonce, onGenerateTheme, proxyUrl, cookieSync, persist, autoApply, children, }: DhemeSetupProps): Promise<React.ReactElement>;
129
+
130
+ export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, DhemeSetup, type DhemeSetupProps, type GenerateThemeStylesOptions };
package/dist/index.d.ts CHANGED
@@ -10,17 +10,80 @@ interface DhemeProviderProps extends DhemeProviderProps$1 {
10
10
  * URL of a proxy route that forwards theme requests server-side,
11
11
  * keeping the API key out of the browser entirely.
12
12
  *
13
+ * Defaults to `"/api/dheme"` when neither `apiKey` nor `onGenerateTheme` is provided.
14
+ * This means the only required setup is creating the route — no need to pass this prop explicitly.
15
+ *
13
16
  * Set up the route with `createDhemeHandler` from `@dheme/next/server`:
14
17
  * @example
15
18
  * // app/api/dheme/route.ts
16
19
  * import { createDhemeHandler } from '@dheme/next/server';
17
20
  * export const { POST } = createDhemeHandler({ apiKey: process.env.DHEME_API_KEY! });
18
21
  *
19
- * // layout.tsx
20
- * <DhemeProvider proxyUrl="/api/dheme" theme="..." />
22
+ * // layout.tsx — proxyUrl is optional, defaults to "/api/dheme"
23
+ * <DhemeProvider theme="..." themeParams={...} />
21
24
  */
22
25
  proxyUrl?: string;
23
26
  }
27
+ interface DhemeScriptProps {
28
+ /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
29
+ apiKey?: string;
30
+ theme: string;
31
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
32
+ defaultMode?: ThemeMode;
33
+ baseUrl?: string;
34
+ nonce?: string;
35
+ /**
36
+ * Custom theme generation function. When provided, replaces the SDK client's
37
+ * generateTheme call entirely. Useful for internal use cases with custom endpoints.
38
+ *
39
+ * @example
40
+ * // Call an internal proxy route without API key:
41
+ * onGenerateTheme={async (params) => {
42
+ * const res = await fetch('/api/generate-theme/proxy', {
43
+ * method: 'POST',
44
+ * headers: { 'Content-Type': 'application/json' },
45
+ * body: JSON.stringify(params),
46
+ * });
47
+ * return res.json();
48
+ * }}
49
+ */
50
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
51
+ }
52
+ interface DhemeSetupProps {
53
+ /** Primary color hex for the theme (e.g. "7C3AED" or "#7C3AED") */
54
+ theme: string;
55
+ /**
56
+ * Default color mode. Passed to both DhemeScript and DhemeProvider — single source of truth.
57
+ * @default 'light'
58
+ */
59
+ defaultMode?: ThemeMode;
60
+ /** Additional theme generation parameters */
61
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
62
+ /** API key for the Dheme service. Server-side only — never sent to the browser. */
63
+ apiKey?: string;
64
+ /** Override the base URL for the Dheme API */
65
+ baseUrl?: string;
66
+ /** Nonce for Content Security Policy */
67
+ nonce?: string;
68
+ /**
69
+ * Server-side custom theme generation function.
70
+ * Replaces the SDK client call in DhemeScript. NOT forwarded to DhemeProvider.
71
+ * For client-side custom generation, use DhemeProvider directly with onGenerateTheme.
72
+ */
73
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
74
+ /**
75
+ * URL of the client-side proxy route.
76
+ * Defaults to "/api/dheme" when no apiKey is provided.
77
+ */
78
+ proxyUrl?: string;
79
+ /** Sync active mode and theme params to cookies for SSR hydration. @default true */
80
+ cookieSync?: boolean;
81
+ /** Persist theme data in localStorage for instant cache hits. @default true */
82
+ persist?: boolean;
83
+ /** Automatically apply CSS variables when theme changes. @default true */
84
+ autoApply?: boolean;
85
+ children: React.ReactNode;
86
+ }
24
87
  interface GenerateThemeStylesOptions {
25
88
  /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
26
89
  apiKey?: string;
@@ -37,4 +100,31 @@ interface GenerateThemeStylesOptions {
37
100
 
38
101
  declare function DhemeProvider({ children, cookieSync, proxyUrl, onThemeChange, onModeChange, onGenerateTheme, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
39
102
 
40
- export { DhemeProvider, type DhemeProviderProps, type GenerateThemeStylesOptions };
103
+ declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme, }: DhemeScriptProps): Promise<React.ReactElement>;
104
+
105
+ /**
106
+ * Combined Server Component that renders DhemeScript + DhemeProvider in a single declaration.
107
+ * Accepts `defaultMode` once, eliminating the need to keep it in sync across two components.
108
+ *
109
+ * @example
110
+ * // app/layout.tsx
111
+ * import { DhemeSetup } from '@dheme/next';
112
+ *
113
+ * export default function RootLayout({ children }: { children: React.ReactNode }) {
114
+ * return (
115
+ * <html>
116
+ * <body>
117
+ * <DhemeSetup theme="7C3AED" defaultMode="dark" apiKey={process.env.DHEME_API_KEY}>
118
+ * {children}
119
+ * </DhemeSetup>
120
+ * </body>
121
+ * </html>
122
+ * );
123
+ * }
124
+ *
125
+ * For apps that need client-side callbacks (onThemeChange, onModeChange, onError, loadingContent),
126
+ * use DhemeScript + DhemeProvider separately.
127
+ */
128
+ declare function DhemeSetup({ theme, defaultMode, themeParams, apiKey, baseUrl, nonce, onGenerateTheme, proxyUrl, cookieSync, persist, autoApply, children, }: DhemeSetupProps): Promise<React.ReactElement>;
129
+
130
+ export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, DhemeSetup, type DhemeSetupProps, type GenerateThemeStylesOptions };
package/dist/index.js CHANGED
@@ -32,14 +32,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
34
  DhemeProvider: () => DhemeProvider,
35
+ DhemeScript: () => DhemeScript,
36
+ DhemeSetup: () => DhemeSetup,
35
37
  ThemeGenerator: () => import_react3.ThemeGenerator,
36
- applyThemeCSSVariables: () => import_react5.applyThemeCSSVariables,
37
- buildCacheKey: () => import_react5.buildCacheKey,
38
- themeToCSS: () => import_react5.themeToCSS,
39
- useDhemeClient: () => import_react4.useDhemeClient,
40
- useGenerateTheme: () => import_react4.useGenerateTheme,
41
- useTheme: () => import_react4.useTheme,
42
- useThemeActions: () => import_react4.useThemeActions
38
+ applyThemeCSSVariables: () => import_react7.applyThemeCSSVariables,
39
+ buildCacheKey: () => import_react7.buildCacheKey,
40
+ themeToCSS: () => import_react7.themeToCSS,
41
+ useDhemeClient: () => import_react6.useDhemeClient,
42
+ useGenerateTheme: () => import_react6.useGenerateTheme,
43
+ useTheme: () => import_react6.useTheme,
44
+ useThemeActions: () => import_react6.useThemeActions
43
45
  });
44
46
  module.exports = __toCommonJS(src_exports);
45
47
 
@@ -47,6 +49,7 @@ module.exports = __toCommonJS(src_exports);
47
49
  var import_react = __toESM(require("react"));
48
50
  var import_react2 = require("@dheme/react");
49
51
  var COOKIE_MAX_AGE = 31536e3;
52
+ var DEFAULT_PROXY_URL = "/api/dheme";
50
53
  function setCookie(name, value) {
51
54
  document.cookie = `${name}=${value};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`;
52
55
  }
@@ -84,9 +87,10 @@ function DhemeProvider({
84
87
  },
85
88
  [cookieSync, onModeChange]
86
89
  );
90
+ const effectiveProxyUrl = proxyUrl ?? (props.apiKey || onGenerateTheme ? void 0 : DEFAULT_PROXY_URL);
87
91
  const proxyGenerateTheme = (0, import_react.useCallback)(
88
92
  async (params) => {
89
- const res = await fetch(proxyUrl, {
93
+ const res = await fetch(effectiveProxyUrl, {
90
94
  method: "POST",
91
95
  headers: { "Content-Type": "application/json" },
92
96
  body: JSON.stringify(params)
@@ -97,9 +101,9 @@ function DhemeProvider({
97
101
  }
98
102
  return res.json();
99
103
  },
100
- [proxyUrl]
104
+ [effectiveProxyUrl]
101
105
  );
102
- const resolvedGenerateTheme = proxyUrl ? proxyGenerateTheme : onGenerateTheme;
106
+ const resolvedGenerateTheme = effectiveProxyUrl ? proxyGenerateTheme : onGenerateTheme;
103
107
  return import_react.default.createElement(import_react2.DhemeProvider, {
104
108
  ...props,
105
109
  theme: primaryColor,
@@ -115,12 +119,137 @@ function DhemeProvider({
115
119
  // src/components/ThemeGenerator.tsx
116
120
  var import_react3 = require("@dheme/react");
117
121
 
122
+ // src/components/DhemeScript.tsx
123
+ var import_react4 = __toESM(require("react"));
124
+ var import_script = __toESM(require("next/script"));
125
+ var import_sdk = require("@dheme/sdk");
126
+ var import_utils = require("@dheme/react/utils");
127
+
128
+ // src/server/cache.ts
129
+ var ThemeCache = class {
130
+ constructor(maxSize = 100, ttlMs = 36e5) {
131
+ this.cache = /* @__PURE__ */ new Map();
132
+ this.maxSize = maxSize;
133
+ this.ttl = ttlMs;
134
+ }
135
+ get(key) {
136
+ const entry = this.cache.get(key);
137
+ if (!entry) return null;
138
+ if (Date.now() - entry.timestamp > this.ttl) {
139
+ this.cache.delete(key);
140
+ return null;
141
+ }
142
+ return entry.data;
143
+ }
144
+ set(key, data) {
145
+ if (this.cache.size >= this.maxSize) {
146
+ const oldestKey = this.cache.keys().next().value;
147
+ if (oldestKey !== void 0) {
148
+ this.cache.delete(oldestKey);
149
+ }
150
+ }
151
+ this.cache.set(key, { data, timestamp: Date.now() });
152
+ }
153
+ clear() {
154
+ this.cache.clear();
155
+ }
156
+ };
157
+ var themeCache = new ThemeCache();
158
+
159
+ // src/components/DhemeScript.tsx
160
+ async function DhemeScript({
161
+ apiKey,
162
+ theme,
163
+ themeParams,
164
+ defaultMode = "light",
165
+ baseUrl,
166
+ nonce,
167
+ onGenerateTheme
168
+ }) {
169
+ const params = { theme, ...themeParams };
170
+ const cacheKey = (0, import_utils.buildCacheKey)(params);
171
+ let themeData = themeCache.get(cacheKey);
172
+ if (!themeData) {
173
+ if (onGenerateTheme) {
174
+ themeData = await onGenerateTheme(params);
175
+ } else {
176
+ const client = new import_sdk.DhemeClient({ apiKey, baseUrl });
177
+ const response = await client.generateTheme(params);
178
+ themeData = response.data;
179
+ }
180
+ themeCache.set(cacheKey, themeData);
181
+ }
182
+ const tailwindVersion = params.tailwindVersion ?? "v4";
183
+ const lightCSS = (0, import_utils.themeToCSS)(themeData, "light", tailwindVersion);
184
+ const darkCSS = (0, import_utils.themeToCSS)(themeData, "dark", tailwindVersion);
185
+ const styleContent = `:root{${lightCSS}}.dark{${darkCSS}}`;
186
+ const scriptContent = (0, import_utils.getNextBlockingScriptPayload)(defaultMode);
187
+ const styleProps = {
188
+ nonce,
189
+ precedence: "high",
190
+ dangerouslySetInnerHTML: { __html: styleContent }
191
+ };
192
+ return import_react4.default.createElement(
193
+ import_react4.default.Fragment,
194
+ null,
195
+ import_react4.default.createElement("style", styleProps),
196
+ // strategy="beforeInteractive" causes Next.js to inject this script into
197
+ // the HTML before any page JS runs — prevents dark/light mode flash.
198
+ import_react4.default.createElement(import_script.default, {
199
+ id: "dheme-mode-detect",
200
+ strategy: "beforeInteractive",
201
+ nonce,
202
+ dangerouslySetInnerHTML: { __html: scriptContent }
203
+ })
204
+ );
205
+ }
206
+
207
+ // src/components/DhemeSetup.tsx
208
+ var import_react5 = __toESM(require("react"));
209
+ async function DhemeSetup({
210
+ // Shared between script and provider
211
+ theme,
212
+ defaultMode = "light",
213
+ themeParams,
214
+ // DhemeScript only (server-side — never reaches the browser)
215
+ apiKey,
216
+ baseUrl,
217
+ nonce,
218
+ onGenerateTheme,
219
+ // DhemeProvider only (client-side)
220
+ proxyUrl,
221
+ cookieSync,
222
+ persist,
223
+ autoApply,
224
+ children
225
+ }) {
226
+ return import_react5.default.createElement(
227
+ import_react5.default.Fragment,
228
+ null,
229
+ // Server Component: fetches theme server-side, emits render-blocking <style> + <script>
230
+ await DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme }),
231
+ // Client Component: provides theme context, handles runtime interactivity
232
+ import_react5.default.createElement(DhemeProvider, {
233
+ theme,
234
+ themeParams,
235
+ defaultMode,
236
+ proxyUrl,
237
+ cookieSync,
238
+ persist,
239
+ autoApply,
240
+ children
241
+ })
242
+ );
243
+ }
244
+
118
245
  // src/index.ts
119
- var import_react4 = require("@dheme/react");
120
- var import_react5 = require("@dheme/react");
246
+ var import_react6 = require("@dheme/react");
247
+ var import_react7 = require("@dheme/react");
121
248
  // Annotate the CommonJS export names for ESM import in node:
122
249
  0 && (module.exports = {
123
250
  DhemeProvider,
251
+ DhemeScript,
252
+ DhemeSetup,
124
253
  ThemeGenerator,
125
254
  applyThemeCSSVariables,
126
255
  buildCacheKey,
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@
4
4
  import React, { useCallback, useRef } from "react";
5
5
  import { DhemeProvider as ReactDhemeProvider } from "@dheme/react";
6
6
  var COOKIE_MAX_AGE = 31536e3;
7
+ var DEFAULT_PROXY_URL = "/api/dheme";
7
8
  function setCookie(name, value) {
8
9
  document.cookie = `${name}=${value};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`;
9
10
  }
@@ -41,9 +42,10 @@ function DhemeProvider({
41
42
  },
42
43
  [cookieSync, onModeChange]
43
44
  );
45
+ const effectiveProxyUrl = proxyUrl ?? (props.apiKey || onGenerateTheme ? void 0 : DEFAULT_PROXY_URL);
44
46
  const proxyGenerateTheme = useCallback(
45
47
  async (params) => {
46
- const res = await fetch(proxyUrl, {
48
+ const res = await fetch(effectiveProxyUrl, {
47
49
  method: "POST",
48
50
  headers: { "Content-Type": "application/json" },
49
51
  body: JSON.stringify(params)
@@ -54,9 +56,9 @@ function DhemeProvider({
54
56
  }
55
57
  return res.json();
56
58
  },
57
- [proxyUrl]
59
+ [effectiveProxyUrl]
58
60
  );
59
- const resolvedGenerateTheme = proxyUrl ? proxyGenerateTheme : onGenerateTheme;
61
+ const resolvedGenerateTheme = effectiveProxyUrl ? proxyGenerateTheme : onGenerateTheme;
60
62
  return React.createElement(ReactDhemeProvider, {
61
63
  ...props,
62
64
  theme: primaryColor,
@@ -72,15 +74,140 @@ function DhemeProvider({
72
74
  // src/components/ThemeGenerator.tsx
73
75
  import { ThemeGenerator } from "@dheme/react";
74
76
 
77
+ // src/components/DhemeScript.tsx
78
+ import React2 from "react";
79
+ import Script from "next/script";
80
+ import { DhemeClient } from "@dheme/sdk";
81
+ import { themeToCSS, buildCacheKey, getNextBlockingScriptPayload } from "@dheme/react/utils";
82
+
83
+ // src/server/cache.ts
84
+ var ThemeCache = class {
85
+ constructor(maxSize = 100, ttlMs = 36e5) {
86
+ this.cache = /* @__PURE__ */ new Map();
87
+ this.maxSize = maxSize;
88
+ this.ttl = ttlMs;
89
+ }
90
+ get(key) {
91
+ const entry = this.cache.get(key);
92
+ if (!entry) return null;
93
+ if (Date.now() - entry.timestamp > this.ttl) {
94
+ this.cache.delete(key);
95
+ return null;
96
+ }
97
+ return entry.data;
98
+ }
99
+ set(key, data) {
100
+ if (this.cache.size >= this.maxSize) {
101
+ const oldestKey = this.cache.keys().next().value;
102
+ if (oldestKey !== void 0) {
103
+ this.cache.delete(oldestKey);
104
+ }
105
+ }
106
+ this.cache.set(key, { data, timestamp: Date.now() });
107
+ }
108
+ clear() {
109
+ this.cache.clear();
110
+ }
111
+ };
112
+ var themeCache = new ThemeCache();
113
+
114
+ // src/components/DhemeScript.tsx
115
+ async function DhemeScript({
116
+ apiKey,
117
+ theme,
118
+ themeParams,
119
+ defaultMode = "light",
120
+ baseUrl,
121
+ nonce,
122
+ onGenerateTheme
123
+ }) {
124
+ const params = { theme, ...themeParams };
125
+ const cacheKey = buildCacheKey(params);
126
+ let themeData = themeCache.get(cacheKey);
127
+ if (!themeData) {
128
+ if (onGenerateTheme) {
129
+ themeData = await onGenerateTheme(params);
130
+ } else {
131
+ const client = new DhemeClient({ apiKey, baseUrl });
132
+ const response = await client.generateTheme(params);
133
+ themeData = response.data;
134
+ }
135
+ themeCache.set(cacheKey, themeData);
136
+ }
137
+ const tailwindVersion = params.tailwindVersion ?? "v4";
138
+ const lightCSS = themeToCSS(themeData, "light", tailwindVersion);
139
+ const darkCSS = themeToCSS(themeData, "dark", tailwindVersion);
140
+ const styleContent = `:root{${lightCSS}}.dark{${darkCSS}}`;
141
+ const scriptContent = getNextBlockingScriptPayload(defaultMode);
142
+ const styleProps = {
143
+ nonce,
144
+ precedence: "high",
145
+ dangerouslySetInnerHTML: { __html: styleContent }
146
+ };
147
+ return React2.createElement(
148
+ React2.Fragment,
149
+ null,
150
+ React2.createElement("style", styleProps),
151
+ // strategy="beforeInteractive" causes Next.js to inject this script into
152
+ // the HTML before any page JS runs — prevents dark/light mode flash.
153
+ React2.createElement(Script, {
154
+ id: "dheme-mode-detect",
155
+ strategy: "beforeInteractive",
156
+ nonce,
157
+ dangerouslySetInnerHTML: { __html: scriptContent }
158
+ })
159
+ );
160
+ }
161
+
162
+ // src/components/DhemeSetup.tsx
163
+ import React3 from "react";
164
+ async function DhemeSetup({
165
+ // Shared between script and provider
166
+ theme,
167
+ defaultMode = "light",
168
+ themeParams,
169
+ // DhemeScript only (server-side — never reaches the browser)
170
+ apiKey,
171
+ baseUrl,
172
+ nonce,
173
+ onGenerateTheme,
174
+ // DhemeProvider only (client-side)
175
+ proxyUrl,
176
+ cookieSync,
177
+ persist,
178
+ autoApply,
179
+ children
180
+ }) {
181
+ return React3.createElement(
182
+ React3.Fragment,
183
+ null,
184
+ // Server Component: fetches theme server-side, emits render-blocking <style> + <script>
185
+ await DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme }),
186
+ // Client Component: provides theme context, handles runtime interactivity
187
+ React3.createElement(DhemeProvider, {
188
+ theme,
189
+ themeParams,
190
+ defaultMode,
191
+ proxyUrl,
192
+ cookieSync,
193
+ persist,
194
+ autoApply,
195
+ children
196
+ })
197
+ );
198
+ }
199
+
75
200
  // src/index.ts
76
201
  import { useTheme, useThemeActions, useGenerateTheme, useDhemeClient } from "@dheme/react";
77
- import { themeToCSS, applyThemeCSSVariables, buildCacheKey } from "@dheme/react";
202
+ import { themeToCSS as themeToCSS2, applyThemeCSSVariables, buildCacheKey as buildCacheKey2 } from "@dheme/react";
78
203
  export {
79
204
  DhemeProvider,
205
+ DhemeScript,
206
+ DhemeSetup,
80
207
  ThemeGenerator,
81
208
  applyThemeCSSVariables,
82
- buildCacheKey,
83
- themeToCSS,
209
+ buildCacheKey2 as buildCacheKey,
210
+ themeToCSS2 as themeToCSS,
84
211
  useDhemeClient,
85
212
  useGenerateTheme,
86
213
  useTheme,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dheme/next",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Next.js App Router bindings for Dheme SDK with server-side theme generation",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@dheme/sdk": "^1.1.0",
59
- "@dheme/react": "^2.13.0"
59
+ "@dheme/react": "^2.15.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/react": "^18.2.0",