create-lumen 2.0.0-alpha.1 → 2.0.0-beta.1

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 (190) hide show
  1. package/README.md +38 -12
  2. package/bin/cli.js +3 -1
  3. package/package.json +3 -3
  4. package/schema/fixtures/v2/invalid/shadcn-without-tailwind.json +1 -1
  5. package/schema/fixtures/v2/valid/react-vite-type-based.json +1 -1
  6. package/schema/lumen.config.v2.json +0 -1
  7. package/src/cli-args.js +169 -7
  8. package/src/configure.js +12 -4
  9. package/src/css.js +14 -30
  10. package/src/dependencies.js +36 -20
  11. package/src/engine/capabilities.js +195 -4
  12. package/src/engine/composition.js +736 -0
  13. package/src/engine/index.js +1 -0
  14. package/src/engine/providers/base-provider.js +4 -4
  15. package/src/injector.js +294 -446
  16. package/src/main.js +38 -16
  17. package/src/manifest/emit.js +1 -1
  18. package/src/manifest/schema.js +1 -1
  19. package/src/prompts.js +581 -238
  20. package/src/readme.js +23 -2
  21. package/src/scaffold.js +17 -0
  22. package/templates/architectures/hybrid/main.jsx +10 -0
  23. package/templates/architectures/hybrid/main.tsx +10 -0
  24. package/templates/architectures/hybrid/src/App.jsx +20 -0
  25. package/templates/architectures/hybrid/src/App.tsx +20 -0
  26. package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +31 -0
  27. package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +36 -0
  28. package/templates/architectures/hybrid/src/shared/components/ui/index.js +1 -0
  29. package/templates/architectures/hybrid/src/shared/components/ui/index.ts +1 -0
  30. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +28 -0
  31. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +28 -0
  32. package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
  33. package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
  34. package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
  35. package/templates/architectures/hybrid/src/shared/types/index.js +20 -0
  36. package/templates/architectures/hybrid/src/shared/types/index.ts +16 -0
  37. package/templates/architectures/hybrid/src/shared/utils/cn.jsx +3 -0
  38. package/templates/architectures/hybrid/src/shared/utils/cn.ts +5 -0
  39. package/templates/architectures/none/main.jsx +10 -0
  40. package/templates/architectures/none/main.tsx +10 -0
  41. package/templates/architectures/none/src/App.jsx +15 -0
  42. package/templates/architectures/none/src/App.tsx +15 -0
  43. package/templates/architectures/none/src/styles/.gitkeep +1 -0
  44. package/templates/architectures/none/src/types/index.js +20 -0
  45. package/templates/architectures/none/src/types/index.ts +16 -0
  46. package/templates/bases/next/app-router/js/.provenance.json +19 -0
  47. package/templates/bases/next/app-router/js/README.md +36 -0
  48. package/templates/bases/next/app-router/js/jsconfig.json +7 -0
  49. package/templates/bases/next/app-router/js/next.config.mjs +6 -0
  50. package/templates/bases/next/app-router/js/package.json +15 -0
  51. package/templates/bases/next/app-router/js/public/file.svg +1 -0
  52. package/templates/bases/next/app-router/js/public/globe.svg +1 -0
  53. package/templates/bases/next/app-router/js/public/next.svg +1 -0
  54. package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
  55. package/templates/bases/next/app-router/js/public/window.svg +1 -0
  56. package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
  57. package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
  58. package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
  59. package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
  60. package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
  61. package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
  62. package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
  63. package/templates/bases/next/app-router/js/src/app/page.js +69 -0
  64. package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
  65. package/templates/bases/next/app-router/ts/.provenance.json +19 -0
  66. package/templates/bases/next/app-router/ts/README.md +36 -0
  67. package/templates/bases/next/app-router/ts/next.config.ts +7 -0
  68. package/templates/bases/next/app-router/ts/package.json +21 -0
  69. package/templates/bases/next/app-router/ts/public/file.svg +1 -0
  70. package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
  71. package/templates/bases/next/app-router/ts/public/next.svg +1 -0
  72. package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
  73. package/templates/bases/next/app-router/ts/public/window.svg +1 -0
  74. package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
  75. package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
  76. package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
  77. package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
  78. package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
  79. package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
  80. package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
  81. package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
  82. package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
  83. package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
  84. package/templates/bases/next/pages-router/js/.provenance.json +19 -0
  85. package/templates/bases/next/pages-router/js/README.md +40 -0
  86. package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
  87. package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
  88. package/templates/bases/next/pages-router/js/package.json +15 -0
  89. package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
  90. package/templates/bases/next/pages-router/js/public/file.svg +1 -0
  91. package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
  92. package/templates/bases/next/pages-router/js/public/next.svg +1 -0
  93. package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
  94. package/templates/bases/next/pages-router/js/public/window.svg +1 -0
  95. package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
  96. package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
  97. package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
  98. package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
  99. package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
  100. package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
  101. package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
  102. package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
  103. package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
  104. package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
  105. package/templates/bases/next/pages-router/ts/README.md +40 -0
  106. package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
  107. package/templates/bases/next/pages-router/ts/package.json +21 -0
  108. package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
  109. package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
  110. package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
  111. package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
  112. package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
  113. package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
  114. package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
  115. package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
  116. package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
  117. package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
  118. package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
  119. package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
  120. package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
  121. package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
  122. package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
  123. package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
  124. package/templates/conditional/linting/biome/biome.json +31 -0
  125. package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
  126. package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
  127. package/templates/conditional/next/config/node/next.config.mjs +6 -0
  128. package/templates/conditional/next/config/node/next.config.ts +7 -0
  129. package/templates/conditional/next/config/static/next.config.mjs +6 -0
  130. package/templates/conditional/next/config/static/next.config.ts +7 -0
  131. package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
  132. package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
  133. package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
  134. package/templates/conditional/next/config/vercel/next.config.ts +7 -0
  135. package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
  136. package/templates/conditional/next/config/webpack/next.config.ts +10 -0
  137. package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
  138. package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
  139. package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
  140. package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
  141. package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
  142. package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
  143. package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
  144. package/templates/conditional/next/font/src/app/layout.js +25 -0
  145. package/templates/conditional/next/font/src/app/layout.tsx +30 -0
  146. package/templates/conditional/next/font/src/lib/fonts.js +11 -0
  147. package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
  148. package/templates/conditional/next/font/src/pages/_app.js +20 -0
  149. package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
  150. package/templates/conditional/next/font/src/pages/_document.js +18 -0
  151. package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
  152. package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
  153. package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
  154. package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
  155. package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
  156. package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
  157. package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
  158. package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
  159. package/templates/conditional/next/image/src/components/image/index.js +1 -0
  160. package/templates/conditional/next/image/src/components/image/index.ts +2 -0
  161. package/templates/conditional/next/server-components/README.md +13 -0
  162. package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
  163. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
  164. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
  165. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
  166. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
  167. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
  168. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
  169. package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
  170. package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
  171. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +11 -0
  172. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +11 -0
  173. package/templates/conditional/testing/jest/none/src/__tests__/App.test.jsx +11 -0
  174. package/templates/conditional/testing/jest/none/src/__tests__/App.test.tsx +11 -0
  175. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +10 -0
  176. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.tsx +10 -0
  177. package/templates/conditional/testing/vitest/none/src/__tests__/App.test.jsx +10 -0
  178. package/templates/conditional/testing/vitest/none/src/__tests__/App.test.tsx +10 -0
  179. package/templates/css/tailwind/src/globals.css +10 -0
  180. package/templates/css/tailwind/src/themes.css +86 -4
  181. package/templates/ui/shadcn/lib/utils.js +6 -0
  182. package/templates/ui/shadcn/lib/utils.ts +6 -0
  183. package/templates/ui/shadcn/primitives/Button.jsx +48 -0
  184. package/templates/ui/shadcn/primitives/Button.tsx +59 -0
  185. package/templates/ui/shadcn/primitives/Card.jsx +55 -0
  186. package/templates/ui/shadcn/primitives/Card.tsx +73 -0
  187. package/templates/ui/shadcn/primitives/index.js +9 -0
  188. package/templates/ui/shadcn/primitives/index.ts +9 -0
  189. package/templates/css/bootstrap/src/themes.css +0 -14
  190. /package/templates/{css/bootstrap/src/globals.css → architectures/hybrid/src/features/.gitkeep} +0 -0
@@ -0,0 +1,18 @@
1
+ import { Html, Head, Main, NextScript } from "next/document";
2
+
3
+ /**
4
+ * Pages Router Custom Document.
5
+ * Note: Per Next.js compiler specifications, next/font loaders are configured
6
+ * in pages/_app to avoid the 'Cannot be used within pages/_document' compile error.
7
+ */
8
+ export default function Document() {
9
+ return (
10
+ <Html lang="en">
11
+ <Head />
12
+ <body className="antialiased">
13
+ <Main />
14
+ <NextScript />
15
+ </body>
16
+ </Html>
17
+ );
18
+ }
@@ -0,0 +1,15 @@
1
+ # next/image Build Size and Performance Impact
2
+
3
+ ## Built-In Optimization
4
+ `next/image` extends standard HTML `<img>` elements with automatic visual stability and performance optimizations:
5
+ 1. **Zero Cumulative Layout Shift (CLS)**: Enforces explicit `width` and `height` dimensions or fills the parent container with `fill`, reserving geometric space before the bitmap payload loads.
6
+ 2. **Modern Format Negotiation**: Serves WebP and AVIF modern formats dynamically when supported by the requesting browser, yielding 30–50% bandwidth reduction compared to legacy JPEG/PNG.
7
+ 3. **Native Lazy Loading**: Viewport-aware on-demand loading so off-screen images do not delay initial page interactivity. The `priority` property allows opting into preloading for Largest Contentful Paint (LCP) elements.
8
+
9
+ ## Bundle Footprint
10
+ - **Runtime JavaScript Overhead**: ~5 KB gzipped client-side runtime handling IntersectionObserver and responsive `srcSet` generation.
11
+ - **Build Impact**: Zero additional build time overhead during static compilation when referencing local SVGs or remote URLs; image transformations are handled on-demand by the Next.js server image optimizer.
12
+
13
+ ## Router Parity
14
+ - **App Router**: Fully compatible with both Server Components (static rendering) and Client Components (`"use client"`).
15
+ - **Pages Router**: Fully compatible with all page and component hierarchies.
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import Image from "next/image";
3
+
4
+ export function ImageExample({
5
+ src = "/next.svg",
6
+ alt = "Next.js logo",
7
+ width = 180,
8
+ height = 37,
9
+ priority = false,
10
+ className = "",
11
+ caption = "Optimized with next/image",
12
+ }) {
13
+ return (
14
+ <figure className={`inline-flex flex-col items-center gap-2 p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-black/20 ${className}`}>
15
+ <div className="relative flex items-center justify-center p-2">
16
+ <Image
17
+ src={src}
18
+ alt={alt}
19
+ width={width}
20
+ height={height}
21
+ priority={priority}
22
+ className="dark:invert transition-transform hover:scale-105"
23
+ />
24
+ </div>
25
+ {caption && (
26
+ <figcaption className="text-xs text-gray-500 dark:text-gray-400 text-center font-mono">
27
+ {caption}
28
+ </figcaption>
29
+ )}
30
+ </figure>
31
+ );
32
+ }
33
+
34
+ export default ImageExample;
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import Image from "next/image";
3
+
4
+ export interface ImageExampleProps {
5
+ src?: string;
6
+ alt?: string;
7
+ width?: number;
8
+ height?: number;
9
+ priority?: boolean;
10
+ className?: string;
11
+ caption?: string;
12
+ }
13
+
14
+ export function ImageExample({
15
+ src = "/next.svg",
16
+ alt = "Next.js logo",
17
+ width = 180,
18
+ height = 37,
19
+ priority = false,
20
+ className = "",
21
+ caption = "Optimized with next/image",
22
+ }: ImageExampleProps) {
23
+ return (
24
+ <figure className={`inline-flex flex-col items-center gap-2 p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-black/20 ${className}`}>
25
+ <div className="relative flex items-center justify-center p-2">
26
+ <Image
27
+ src={src}
28
+ alt={alt}
29
+ width={width}
30
+ height={height}
31
+ priority={priority}
32
+ className="dark:invert transition-transform hover:scale-105"
33
+ />
34
+ </div>
35
+ {caption && (
36
+ <figcaption className="text-xs text-gray-500 dark:text-gray-400 text-center font-mono">
37
+ {caption}
38
+ </figcaption>
39
+ )}
40
+ </figure>
41
+ );
42
+ }
43
+
44
+ export default ImageExample;
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import Image from "next/image";
3
+
4
+ export function ImageExample({
5
+ src = "/next.svg",
6
+ alt = "Next.js logo",
7
+ width = 180,
8
+ height = 37,
9
+ priority = false,
10
+ className = "",
11
+ caption = "Optimized with next/image",
12
+ }) {
13
+ return (
14
+ <figure className={`inline-flex flex-col items-center gap-2 p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-black/20 ${className}`}>
15
+ <div className="relative flex items-center justify-center p-2">
16
+ <Image
17
+ src={src}
18
+ alt={alt}
19
+ width={width}
20
+ height={height}
21
+ priority={priority}
22
+ className="dark:invert transition-transform hover:scale-105"
23
+ />
24
+ </div>
25
+ {caption && (
26
+ <figcaption className="text-xs text-gray-500 dark:text-gray-400 text-center font-mono">
27
+ {caption}
28
+ </figcaption>
29
+ )}
30
+ </figure>
31
+ );
32
+ }
33
+
34
+ export default ImageExample;
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import Image from "next/image";
3
+
4
+ export interface ImageExampleProps {
5
+ src?: string;
6
+ alt?: string;
7
+ width?: number;
8
+ height?: number;
9
+ priority?: boolean;
10
+ className?: string;
11
+ caption?: string;
12
+ }
13
+
14
+ export function ImageExample({
15
+ src = "/next.svg",
16
+ alt = "Next.js logo",
17
+ width = 180,
18
+ height = 37,
19
+ priority = false,
20
+ className = "",
21
+ caption = "Optimized with next/image",
22
+ }: ImageExampleProps) {
23
+ return (
24
+ <figure className={`inline-flex flex-col items-center gap-2 p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-black/20 ${className}`}>
25
+ <div className="relative flex items-center justify-center p-2">
26
+ <Image
27
+ src={src}
28
+ alt={alt}
29
+ width={width}
30
+ height={height}
31
+ priority={priority}
32
+ className="dark:invert transition-transform hover:scale-105"
33
+ />
34
+ </div>
35
+ {caption && (
36
+ <figcaption className="text-xs text-gray-500 dark:text-gray-400 text-center font-mono">
37
+ {caption}
38
+ </figcaption>
39
+ )}
40
+ </figure>
41
+ );
42
+ }
43
+
44
+ export default ImageExample;
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import Image from "next/image";
3
+
4
+ /**
5
+ * Universal Next.js Image component example.
6
+ * Works seamlessly in both App Router (Server and Client components)
7
+ * and Pages Router.
8
+ *
9
+ * Demonstrates:
10
+ * - Built-in automatic format optimization (WebP/AVIF)
11
+ * - Intrinsic sizing to avoid layout shift (CLS)
12
+ * - Priority loading option for LCP optimization
13
+ */
14
+ export function ImageExample({
15
+ src = "/next.svg",
16
+ alt = "Next.js logo",
17
+ width = 180,
18
+ height = 37,
19
+ priority = false,
20
+ className = "",
21
+ caption = "Optimized with next/image",
22
+ }) {
23
+ return (
24
+ <figure className={`inline-flex flex-col items-center gap-2 p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-black/20 ${className}`}>
25
+ <div className="relative flex items-center justify-center p-2">
26
+ <Image
27
+ src={src}
28
+ alt={alt}
29
+ width={width}
30
+ height={height}
31
+ priority={priority}
32
+ className="dark:invert transition-transform hover:scale-105"
33
+ />
34
+ </div>
35
+ {caption && (
36
+ <figcaption className="text-xs text-gray-500 dark:text-gray-400 text-center font-mono">
37
+ {caption}
38
+ </figcaption>
39
+ )}
40
+ </figure>
41
+ );
42
+ }
43
+
44
+ export default ImageExample;
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import Image from "next/image";
3
+
4
+ export interface ImageExampleProps {
5
+ src?: string;
6
+ alt?: string;
7
+ width?: number;
8
+ height?: number;
9
+ priority?: boolean;
10
+ className?: string;
11
+ caption?: string;
12
+ }
13
+
14
+ /**
15
+ * Universal Next.js Image component example.
16
+ * Works seamlessly in both App Router (Server and Client components)
17
+ * and Pages Router.
18
+ *
19
+ * Demonstrates:
20
+ * - Built-in automatic format optimization (WebP/AVIF)
21
+ * - Intrinsic sizing to avoid layout shift (CLS)
22
+ * - Priority loading option for LCP optimization
23
+ */
24
+ export function ImageExample({
25
+ src = "/next.svg",
26
+ alt = "Next.js logo",
27
+ width = 180,
28
+ height = 37,
29
+ priority = false,
30
+ className = "",
31
+ caption = "Optimized with next/image",
32
+ }: ImageExampleProps) {
33
+ return (
34
+ <figure className={`inline-flex flex-col items-center gap-2 p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-black/20 ${className}`}>
35
+ <div className="relative flex items-center justify-center p-2">
36
+ <Image
37
+ src={src}
38
+ alt={alt}
39
+ width={width}
40
+ height={height}
41
+ priority={priority}
42
+ className="dark:invert transition-transform hover:scale-105"
43
+ />
44
+ </div>
45
+ {caption && (
46
+ <figcaption className="text-xs text-gray-500 dark:text-gray-400 text-center font-mono">
47
+ {caption}
48
+ </figcaption>
49
+ )}
50
+ </figure>
51
+ );
52
+ }
53
+
54
+ export default ImageExample;
@@ -0,0 +1 @@
1
+ export { ImageExample } from "../ImageExample";
@@ -0,0 +1,2 @@
1
+ export { ImageExample } from "../ImageExample";
2
+ export type { ImageExampleProps } from "../ImageExample";
@@ -0,0 +1,13 @@
1
+ # React Server Components (RSC) Boundaries & Linting
2
+
3
+ This conditional template provides:
4
+ 1. **Server vs Client Boundary Components**: Clean demonstrations of React Server Components composing Client Components with `"use client"`.
5
+ 2. **ESLint Rule Wiring (`rsc/no-server-imports-in-client`)**: An AST-based ESLint rule that disallows importing server-only packages (e.g., `server-only`, Node.js built-ins) into Client Components marked with `"use client"`.
6
+ 3. **Dual Router Compatibility**: Operates seamlessly in both Next.js App Router and Pages Router.
7
+
8
+ ## Components
9
+ - `ClientCounter`: An interactive client component demonstrating React state and event handling.
10
+ - `ServerCard`: A server-rendered component shell passing children and composing `ClientCounter` across the boundary.
11
+
12
+ ## ESLint Configuration
13
+ Wires `rsc/no-server-imports-in-client` and `no-restricted-imports` into Next.js flat configuration (`eslint.config.mjs`).
@@ -0,0 +1,47 @@
1
+ import { noServerImportsInClient } from "./rules/no-server-imports-in-client.mjs";
2
+
3
+ let baseConfig = [];
4
+ try {
5
+ const next = await import("eslint-config-next");
6
+ const imported = next.default || next;
7
+ baseConfig = Array.isArray(imported) ? imported : [imported];
8
+ } catch {
9
+ try {
10
+ const { FlatCompat } = await import("@eslint/eslintrc");
11
+ const { fileURLToPath } = await import("url");
12
+ const { dirname } = await import("path");
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const compat = new FlatCompat({ baseDirectory: __dirname });
16
+ baseConfig = compat.extends("next/core-web-vitals");
17
+ } catch {}
18
+ }
19
+
20
+ const eslintConfig = [
21
+ ...baseConfig,
22
+ {
23
+ plugins: {
24
+ rsc: {
25
+ rules: {
26
+ "no-server-imports-in-client": noServerImportsInClient,
27
+ },
28
+ },
29
+ },
30
+ rules: {
31
+ "rsc/no-server-imports-in-client": "error",
32
+ "no-restricted-imports": [
33
+ "error",
34
+ {
35
+ patterns: [
36
+ {
37
+ group: ["server-only", "**/server-only/**"],
38
+ message: "Do not import server-only packages in client modules.",
39
+ },
40
+ ],
41
+ },
42
+ ],
43
+ },
44
+ },
45
+ ];
46
+
47
+ export default eslintConfig;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ESLint Rule: rsc/no-server-imports-in-client
3
+ *
4
+ * Enforces server/client boundaries in React Server Components (RSC).
5
+ * When a file is marked with the "use client" directive, it is prohibited
6
+ * from importing server-only packages (e.g. 'server-only', Node.js builtins,
7
+ * or modules from server-designated paths).
8
+ */
9
+
10
+ export const noServerImportsInClient = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Disallow server-only imports in client components marked with 'use client'",
15
+ category: "Possible Errors",
16
+ recommended: true,
17
+ },
18
+ schema: [
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ serverModules: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ },
26
+ },
27
+ additionalProperties: false,
28
+ },
29
+ ],
30
+ messages: {
31
+ noServerImport:
32
+ "Server-only module '{{name}}' cannot be imported into a Client Component ('use client'). Keep server logic on the server side of the RSC boundary.",
33
+ },
34
+ },
35
+ create(context) {
36
+ const options = context.options[0] || {};
37
+ const restrictedSet = new Set(
38
+ options.serverModules || [
39
+ "server-only",
40
+ "node:fs",
41
+ "node:fs/promises",
42
+ "fs",
43
+ "fs/promises",
44
+ "node:child_process",
45
+ "child_process",
46
+ "node:net",
47
+ "net",
48
+ "node:tls",
49
+ "tls",
50
+ ]
51
+ );
52
+
53
+ let isClientComponent = false;
54
+
55
+ return {
56
+ Program(node) {
57
+ // Inspect top-level statements for "use client" directive
58
+ for (const stmt of node.body) {
59
+ if (
60
+ stmt.type === "ExpressionStatement" &&
61
+ stmt.expression.type === "Literal" &&
62
+ stmt.expression.value === "use client"
63
+ ) {
64
+ isClientComponent = true;
65
+ break;
66
+ }
67
+ }
68
+ },
69
+ ImportDeclaration(node) {
70
+ if (!isClientComponent) return;
71
+ const source = node.source.value;
72
+ if (
73
+ restrictedSet.has(source) ||
74
+ source.startsWith("server-only") ||
75
+ source.includes("/server/") ||
76
+ source.endsWith("/server")
77
+ ) {
78
+ context.report({
79
+ node,
80
+ messageId: "noServerImport",
81
+ data: { name: source },
82
+ });
83
+ }
84
+ },
85
+ };
86
+ },
87
+ };
88
+
89
+ export default noServerImportsInClient;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ESLint Rule: rsc/no-server-imports-in-client
3
+ *
4
+ * Enforces server/client boundaries in React Server Components (RSC).
5
+ * When a file is marked with the "use client" directive, it is prohibited
6
+ * from importing server-only packages (e.g. 'server-only', Node.js builtins,
7
+ * or modules from server-designated paths).
8
+ */
9
+
10
+ export const noServerImportsInClient = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Disallow server-only imports in client components marked with 'use client'",
15
+ category: "Possible Errors",
16
+ recommended: true,
17
+ },
18
+ schema: [
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ serverModules: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ },
26
+ },
27
+ additionalProperties: false,
28
+ },
29
+ ],
30
+ messages: {
31
+ noServerImport:
32
+ "Server-only module '{{name}}' cannot be imported into a Client Component ('use client'). Keep server logic on the server side of the RSC boundary.",
33
+ },
34
+ },
35
+ create(context) {
36
+ const options = context.options[0] || {};
37
+ const restrictedSet = new Set(
38
+ options.serverModules || [
39
+ "server-only",
40
+ "node:fs",
41
+ "node:fs/promises",
42
+ "fs",
43
+ "fs/promises",
44
+ "node:child_process",
45
+ "child_process",
46
+ "node:net",
47
+ "net",
48
+ "node:tls",
49
+ "tls",
50
+ ]
51
+ );
52
+
53
+ let isClientComponent = false;
54
+
55
+ return {
56
+ Program(node) {
57
+ for (const stmt of node.body) {
58
+ if (
59
+ stmt.type === "ExpressionStatement" &&
60
+ stmt.expression.type === "Literal" &&
61
+ stmt.expression.value === "use client"
62
+ ) {
63
+ isClientComponent = true;
64
+ break;
65
+ }
66
+ }
67
+ },
68
+ ImportDeclaration(node) {
69
+ if (!isClientComponent) return;
70
+ const source = node.source.value;
71
+ if (
72
+ restrictedSet.has(source) ||
73
+ source.startsWith("server-only") ||
74
+ source.includes("/server/") ||
75
+ source.endsWith("/server")
76
+ ) {
77
+ context.report({
78
+ node,
79
+ messageId: "noServerImport",
80
+ data: { name: source },
81
+ });
82
+ }
83
+ },
84
+ };
85
+ },
86
+ };
87
+
88
+ export default noServerImportsInClient;
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+
5
+ /**
6
+ * Client Component example for RSC boundary.
7
+ * Marked with "use client" directive — renders on client with full interactivity,
8
+ * React hooks, and state management.
9
+ */
10
+ export function ClientCounter({ initialCount = 0, children }) {
11
+ const [count, setCount] = useState(initialCount);
12
+
13
+ return (
14
+ <div className="client-boundary p-4 rounded-lg border border-blue-500/20 bg-blue-500/5 my-4">
15
+ <div className="flex items-center gap-2 mb-2">
16
+ <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900/60 dark:text-blue-300">
17
+ Client Component (&quot;use client&quot;)
18
+ </span>
19
+ </div>
20
+ <p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
21
+ Interactive client boundary component. Manages state and handles user interaction.
22
+ </p>
23
+ <div className="flex items-center gap-3">
24
+ <button
25
+ type="button"
26
+ onClick={() => setCount((c) => c + 1)}
27
+ className="px-3 py-1.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors cursor-pointer"
28
+ >
29
+ Count: {count}
30
+ </button>
31
+ <button
32
+ type="button"
33
+ onClick={() => setCount(0)}
34
+ className="px-2 py-1.5 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 cursor-pointer"
35
+ >
36
+ Reset
37
+ </button>
38
+ </div>
39
+ {children && (
40
+ <div className="mt-4 pt-3 border-t border-blue-500/10">
41
+ {children}
42
+ </div>
43
+ )}
44
+ </div>
45
+ );
46
+ }
47
+
48
+ export default ClientCounter;
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+
5
+ export interface ClientCounterProps {
6
+ initialCount?: number;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ /**
11
+ * Client Component example for RSC boundary.
12
+ * Marked with "use client" directive — renders on client with full interactivity,
13
+ * React hooks, and state management.
14
+ */
15
+ export function ClientCounter({ initialCount = 0, children }: ClientCounterProps) {
16
+ const [count, setCount] = useState(initialCount);
17
+
18
+ return (
19
+ <div className="client-boundary p-4 rounded-lg border border-blue-500/20 bg-blue-500/5 my-4">
20
+ <div className="flex items-center gap-2 mb-2">
21
+ <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900/60 dark:text-blue-300">
22
+ Client Component (&quot;use client&quot;)
23
+ </span>
24
+ </div>
25
+ <p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
26
+ Interactive client boundary component. Manages state and handles user interaction.
27
+ </p>
28
+ <div className="flex items-center gap-3">
29
+ <button
30
+ type="button"
31
+ onClick={() => setCount((c) => c + 1)}
32
+ className="px-3 py-1.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors cursor-pointer"
33
+ >
34
+ Count: {count}
35
+ </button>
36
+ <button
37
+ type="button"
38
+ onClick={() => setCount(0)}
39
+ className="px-2 py-1.5 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 cursor-pointer"
40
+ >
41
+ Reset
42
+ </button>
43
+ </div>
44
+ {children && (
45
+ <div className="mt-4 pt-3 border-t border-blue-500/10">
46
+ {children}
47
+ </div>
48
+ )}
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default ClientCounter;