create-rsc-kit 0.10.0 → 0.12.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/dist/index.js CHANGED
@@ -139,6 +139,7 @@ function write(o) {
139
139
  ['vite.config.ts', t.viteConfig(o)],
140
140
  ['.gitignore', t.gitignore],
141
141
  ['README.md', t.readme(o)],
142
+ ['AGENTS.md', t.agents(o)],
142
143
  ['src/app/layout.tsx', t.layout(o)],
143
144
  ['src/app/page.tsx', t.page(o)],
144
145
  ['src/components/Counter.tsx', t.counter(o)],
package/dist/init.js CHANGED
@@ -261,6 +261,10 @@ function routes(o, dir) {
261
261
  [join(o.sourceDir, 'app/layout.tsx'), t.layout(o)],
262
262
  [join(o.sourceDir, 'app/page.tsx'), t.page(o)],
263
263
  [join(o.sourceDir, 'components/Counter.tsx'), t.counter(o)],
264
+ // Beside the route tree rather than merged into an existing AGENTS.md: a
265
+ // file of someone else's instructions is not one to append to blind, and
266
+ // the loop below skips it if it is already there.
267
+ ['AGENTS.md', t.agents(o)],
264
268
  ];
265
269
  if (o.tailwind)
266
270
  files.push([join(o.sourceDir, 'app/styles.css'), t.styles]);
@@ -82,4 +82,18 @@ export declare const gitignore = "node_modules\nbuild\n.output\n.rsc\ndist\n*.lo
82
82
  * leaves behind: a new project has nothing to grandfather in.
83
83
  */
84
84
  export declare function oxlintConfig(o: Options): string;
85
+ /**
86
+ * What an agent working in this project needs to know and would otherwise guess.
87
+ *
88
+ * Not a summary of the documentation. Every line here is a thing that is easy
89
+ * to get wrong from React or Next habits and produces code that looks right:
90
+ * the wrong directive, a client component reaching for the request, a check
91
+ * written in the component instead of the action. An agent that knows the
92
+ * documentation exists will still write those, because they are what the
93
+ * neighbouring frameworks taught it.
94
+ *
95
+ * AGENTS.md rather than CLAUDE.md: it is the cross-tool name, and Claude Code
96
+ * reads it too.
97
+ */
98
+ export declare function agents(o: Options): string;
85
99
  export declare function readme(o: Options): string;
package/dist/templates.js CHANGED
@@ -432,6 +432,171 @@ export function oxlintConfig(o) {
432
432
  ignorePatterns: ['build', 'dist', `${o.sourceDir}/rsc-*.d.ts`],
433
433
  }, null, 2) + '\n');
434
434
  }
435
+ /**
436
+ * What an agent working in this project needs to know and would otherwise guess.
437
+ *
438
+ * Not a summary of the documentation. Every line here is a thing that is easy
439
+ * to get wrong from React or Next habits and produces code that looks right:
440
+ * the wrong directive, a client component reaching for the request, a check
441
+ * written in the component instead of the action. An agent that knows the
442
+ * documentation exists will still write those, because they are what the
443
+ * neighbouring frameworks taught it.
444
+ *
445
+ * AGENTS.md rather than CLAUDE.md: it is the cross-tool name, and Claude Code
446
+ * reads it too.
447
+ */
448
+ export function agents(o) {
449
+ const pm = o.host === 'node' ? 'npm run' : 'bun run';
450
+ return `# Working in this project
451
+
452
+ React Server Components through \`@rsc-kit/core\`, a Vite plugin. Routes are
453
+ files under \`${o.sourceDir}/app\`. There is no server file to edit — the build
454
+ generates it.
455
+
456
+ Read the guides at https://rsc-kit.dev before reaching for a pattern from
457
+ another framework. The notes below are only the things most often got wrong.
458
+
459
+ ## Commands
460
+
461
+ \`\`\`sh
462
+ ${pm} dev # vite, with the engine in it
463
+ ${pm} build # builds and prerenders; prints what it froze
464
+ ${pm} typecheck
465
+ \`\`\`
466
+
467
+ **Read the build output.** It is not decoration — it says which routes were
468
+ stored, which render per request, and why:
469
+
470
+ \`\`\`
471
+ ○ /account 85 kB
472
+ ◐ /locale 85 kB
473
+ dynamic — called cookies(), headers()
474
+ \`\`\`
475
+
476
+ If a page you expected to be static is not, the reason is on that line. Do not
477
+ guess at it.
478
+
479
+ ## Server and client
480
+
481
+ Every component is a **server** component unless its file starts with
482
+ \`"use client"\`. Server components can be \`async\` and read the database
483
+ directly. They do not ship to the browser.
484
+
485
+ Add \`"use client"\` only when the file needs state, an effect, an event
486
+ handler or a browser API. It is a boundary, not a label: everything that file
487
+ imports goes to the browser too.
488
+
489
+ \`"use server"\` is a different thing and not the opposite. It marks a module
490
+ whose exports may be **called from** the browser — a server action.
491
+
492
+ \`\`\`ts
493
+ 'use server'
494
+ export async function createPost(input) { … } // callable from a client component
495
+ \`\`\`
496
+
497
+ Do not put \`"use server"\` at the top of a page to make it a server component.
498
+ It already is one.
499
+
500
+ ## Reading the request
501
+
502
+ \`cookies()\`, \`headers()\` and \`searchParams()\` come from
503
+ \`@rsc-kit/core/request\` and are **async**. So are a page's \`params\` and
504
+ \`searchParams\` props, and an api route's.
505
+
506
+ Reading any of them makes the page render per request instead of being frozen
507
+ at build time. That is usually correct — just know that it is the trade.
508
+
509
+ \`await connection()\` says "render this per visitor" deliberately, when
510
+ nothing else in the page happens to say it.
511
+
512
+ ## Data
513
+
514
+ Fetch in a server component and await it. There is no loader and no
515
+ \`getServerSideProps\`.
516
+
517
+ Better still, do not await it — pass the promise to a client component and let
518
+ it \`use()\` the value. The shell paints immediately and the data streams into
519
+ the same response, with no request from the browser:
520
+
521
+ \`\`\`tsx
522
+ export default function Page() {
523
+ const posts = getPosts() // not awaited
524
+
525
+ return (
526
+ <Suspense fallback={<Skeleton />}>
527
+ <List posts={posts} /> {/* "use client": use(posts) */}
528
+ </Suspense>
529
+ )
530
+ }
531
+ \`\`\`
532
+
533
+ For data the **browser** decides to fetch — a filter, a refresh — use TanStack
534
+ Query or SWR. This project does not ship a cache and should not grow one.
535
+
536
+ ## Actions, and where the check goes
537
+
538
+ An action is a public endpoint. Anyone can call it directly, so the
539
+ authorisation check belongs **inside the action**, never in the component that
540
+ renders the button.
541
+
542
+ Build actions from a client so the check cannot be forgotten:
543
+
544
+ \`\`\`ts
545
+ 'use server'
546
+ import { createActionClient } from '@rsc-kit/core/action'
547
+
548
+ export const client = createActionClient().use(async ({ next }) => {
549
+ const user = await currentUser()
550
+
551
+ if (!user) throw new ServerAuthenticationError()
552
+
553
+ return next({ ctx: { user } })
554
+ })
555
+
556
+ export const createPost = client.input(schema).handler(async ({ input, ctx }) => …)
557
+ export const listPosts = client.query(async ({ ctx }) => …)
558
+ \`\`\`
559
+
560
+ \`.handler()\` is a mutation, \`.query()\` is a read sent as a GET. Both run
561
+ the middleware, so \`ctx.user\` is typed and non-null inside them.
562
+
563
+ Authorise on **identity, not arguments**. \`deletePost(id)\` that trusts the id
564
+ is an IDOR — the caller chooses the id.
565
+
566
+ Middleware in \`middleware.ts\` guards a route tree. It does **not** run for
567
+ actions, because an action renders no route.
568
+
569
+ ## Urls are input
570
+
571
+ Export a schema beside the page or route and the values arrive parsed and typed:
572
+
573
+ \`\`\`ts
574
+ export const params = z.object({ slug: z.string().min(1) })
575
+ export const searchParams = z.object({ page: z.coerce.number().int().min(1).default(1) })
576
+ \`\`\`
577
+
578
+ Bad \`params\` answer 404, bad \`searchParams\` reach the error boundary. Do
579
+ not hand-parse \`Number(searchParams.get('page'))\`.
580
+
581
+ ## Api routes
582
+
583
+ \`${o.sourceDir}/app/**/route.ts\`, exporting \`GET\`, \`POST\` and so on.
584
+ A real \`Request\` in, a real \`Response\` out. Await \`params\`,
585
+ \`searchParams\` and \`body\` from the second argument.
586
+
587
+ They run their directory's \`middleware.ts\`, and a \`GET\` that reads nothing
588
+ from the request is answered from disk.
589
+
590
+ ## Things that are not this project
591
+
592
+ - No \`pages/\` directory, no \`_app\`, no \`getStaticProps\`.
593
+ - No \`next/link\`, \`next/image\` or \`next/navigation\` — use
594
+ \`@rsc-kit/core/Link\` and \`@rsc-kit/core/navigate\`.
595
+ - No \`express\`/\`fastify\` server to write. Do not add one.
596
+ - Do not install a state manager to move data from server to client. Props and
597
+ promises already cross that boundary.
598
+ `;
599
+ }
435
600
  export function readme(o) {
436
601
  const pm = o.host === 'node' ? 'npm run' : 'bun run';
437
602
  const runtime = o.host === 'worker' ? 'Cloudflare Workers' : o.host === 'node' ? 'Node' : 'Bun';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rsc-kit",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Scaffold an RSC app on Bun, Hono, Elysia or Node.",
6
6
  "bin": {