Skip to content

dogfood: make the .server.ts vs 'use server' distinction unmissable for agents #820

Description

@vivek7405

Problem

Agents confuse the two kinds of .server.ts file:

  • *.server.ts WITHOUT 'use server' = a server-only UTILITY (browser import = throw-at-load stub; must NOT be imported into a page/component).
  • *.server.ts WITH 'use server' = a server ACTION (browser import = a working RPC stub; the way to call the server from client code).

Both share the .server.ts extension, so the directive INSIDE the file is the only discriminator, which is easy to miss.

The after-the-fact diagnostics are already solid (verified this session): the throw-at-load stub message names the 'use server' remedy + a docs link; no-server-import-in-browser-module, use-server-needs-extension, and use-server-exports-callable catch the failure modes with good hints. So this is NOT about better error messages. The gap is FIRST-PASS authoring guidance, so an agent picks right before hitting the error:

  • There is no single crisp decision heuristic surfaced up front.
  • The scaffold's server-only UTILITY examples (db/*.server.ts) carry no comment saying "server-only, no directive, do not import into a page/component."
  • The full-stack DEFAULT ships NO 'use server' example at all (empty modules/, see dogfood: close first-pass friction found building a full-stack app #817), so a full-stack agent only sees no-directive .server.ts utils and may copy that shape for something it wants client-callable.

Design / approach

Surface a one-line decision rule everywhere the agent looks, and make the scaffold examples contrast the two kinds explicitly:

  • Decision heuristic (add to AGENTS.md, the scaffold CONVENTIONS.md, and the MCP add_server_action prompt body): "Will the client call it? -> add 'use server' (it becomes an RPC action). Is it server-only infra (DB, secrets, node:*)? -> no directive, and never import it into a page/component; reach it from a 'use server' action / route.ts / middleware.ts."
  • Commented util example: give the scaffold's server-only utility (db/connection.server.ts or a lib/*.server.ts) a one-line comment stating it is server-only (no directive) and must not be imported into a page/component.
  • Commented action example: ensure the dogfood: close first-pass friction found building a full-stack app #817 full-stack seeded module models a 'use server' query/action WITH a comment explaining the directive is what makes it client-callable, so the default template shows the distinction (today only api/saas do).

Implementation notes (for the implementing agent)

  • Overlaps dogfood: close first-pass friction found building a full-stack app #817 (seeds the full-stack example module + updates the MCP prompts) and dogfood: split ui registry lib/utils.ts so importing cn() doesn't pin a page to the browser #819 (cn.ts). Coordinate wording; this issue is the "teach the .server.ts distinction" slice.
  • Edit in lockstep: packages/cli/templates/AGENTS.md (the .server.ts table region already exists), packages/cli/templates/CONVENTIONS.md, and the MCP PROMPT_BODIES.add_server_action in packages/mcp/src/mcp-docs.js (add a line on the server-only-util contrast). Root AGENTS.md + agent-docs/recipes.md already have the 4-row table; add the one-line heuristic above it.
  • Invariant 11 (prose punctuation) applies.
  • No runtime/check change needed (the diagnostics are already good); this is docs + scaffold-comment + MCP-prompt only. If a test is warranted, assert the scaffold util file carries the server-only comment and the MCP add_server_action prompt mentions the util contrast (packages/mcp/test/*).

Acceptance criteria

  • The one-line "client-callable -> 'use server'; server-only -> no directive, never import into a page/component" heuristic appears in AGENTS.md, scaffold CONVENTIONS.md, and the MCP add_server_action prompt.
  • The scaffold's server-only utility example carries a comment stating it is server-only and must not be imported into a page/component.
  • The full-stack seeded example (dogfood: close first-pass friction found building a full-stack app #817) models a 'use server' action with a directive-explaining comment.
  • Docs/scaffold/MCP tests still pass.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions