agentsclimarketplace

Nfs architecture patterns

Skill juncoding/nextjs-fullstack-starter/skills/nfs-architecture-patterns

Claude Code plugin: scaffold and maintain lightweight back-office apps on pure Next.js (App Router) — Server Components for reads, Server Actions for writes, services in src/server/modules/. No tRPC.

Install
npx -y skills add juncoding/nextjs-fullstack-starter --skill nfs-architecture-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Reference patterns for ongoing development on a Next.js fullstack project scaffolded with nextjs-fullstack-starter — Server Components for reads, Server Actions for writes, services in src/server/modules/. Use this whenever adding a new module, writing a new page or Server Action, deciding between Server Action vs route handler vs MCP tool, wiring permissions, structuring services, handling errors, caching with cacheTag / updateTag, or making any architectural decision in a project that was bootstrapped with this plugin. Triggers on phrases like 'add a new module', 'create a Server Action', 'where should this logic go', 'follow project conventions', 'how do I invalidate the cache', 'should this be a page or an action', or any 'how do I do X in this project' question.

SKILL.md

9.1 KB, as published. Nobody here has run it

Architecture patterns for ongoing development

For projects already scaffolded with nextjs-fullstack-starter. Explains the patterns to follow when adding features. Companion to the nfs-scaffold-app skill which only handles initial setup.

Use this skill when

  • Adding a new business module (e.g. customer, order, invoice).
  • Writing a new page, Server Action, or route handler.
  • Deciding whether something belongs in a page, a Server Action, a route handler, an MCP tool, or a cron job.
  • Wiring permissions on a new resource.
  • Structuring a service that touches multiple modules.
  • Caching with cacheTag / updateTag and not sure when to use which.
  • Handling errors at any layer.
  • Reviewing whether a PR follows project conventions.

The four-rule cheat sheet

  1. src/app/ is a thin delivery layer. No business logic. No DB queries. Just: validate, call a service, return.
  2. src/server/ is the entire backend. Every file starts with import "server-only";.
  3. Permissions live in services. Every service method touching user-owned data takes userId first and calls requirePermission.
  4. Audit calls live in services, inside the same transaction as the mutation.

If you remember nothing else, remember these four.

Reference index

Read the file matching your task:

Doing this...Read this
Creating a new business module (service, schema, types)references/service-layer.md
Writing a Server Component page that reads datareferences/server-components-and-pages.md
Writing a Server Action for a write/mutationreferences/server-actions.md
Caching reads, invalidating after writesreferences/caching.md
Wiring auth / RBAC for a new resourcereferences/permissions-and-audit.md
Adding a REST endpoint (webhook, third-party callable, file download)references/route-handlers.md
Throwing / catching errors at any layerreferences/error-handling.md

The delivery-layer matrix

When you have new functionality, decide which delivery layer it lives in:

CallerDelivery layer
The app's own UI — reading dataAsync Server Component page in src/app/(dashboard)/<feature>/page.tsx → service
The app's own UI — writing dataServer Action in src/server/actions/<feature>.actions.ts → service
An AI client (Claude Desktop, Cursor)MCP tool in src/server/mcp/tools/ (wraps the same service)
A webhook (Stripe, Resend, Svix-signed)Route handler at src/app/api/webhooks/<provider>/route.ts
A scheduled jobCron registration in src/server/jobs/, kicked by instrumentation.ts
A third-party that needs RESTRoute handler at src/app/api/v1/<resource>/route.ts
File upload / downloadRoute handler (Web Streams API)
A testDirect service call with mocked Prisma, or createCaller-style test harness if you build one

All of these end up calling the same service method — only the wrapper layer differs.

Server Component vs. client component

Default: Server Component. Add "use client" only when you need:

  • React hooks (useState, useEffect, etc.)
  • Browser-only APIs (window, document, localStorage, IntersectionObserver)
  • Event handlers (onClick, onChange, onSubmit — though <form action> works without client JS)
  • Third-party libraries that explicitly need a client (cmdk, framer-motion, etc.)

When you need client interactivity over server-fetched data, fetch on the server and pass data in as a prop:

// page.tsx — Server Component, fetches data
import { CustomerFilters } from "./_components/filters";  // client component

export default async function Page() {
  const session = await requireSession();
  const customers = await customerService.list(session.user.id, {});
  return <CustomerFilters initial={customers} />;
}

Don't fetch data inside client components by spinning up a route handler just to feed them — that's reintroducing the JSON layer you came here to avoid.

Server Action vs. route handler

Both are POST handlers. Pick by caller:

Server ActionRoute handler
CallerThe app's own UI (forms / buttons)Anything else — webhooks, mobile, AI, scripts
URLNone — invoked by referenceReal URL — /api/...
Body shapeFormData or any serializable JS valueArbitrary HTTP body
Use revalidate?Yes — revalidatePath / updateTag after the mutationNo — the caller manages their own state
Best forForms, button-click mutations, anything triggered by the UIAnything triggered by something external

If you find yourself writing fetch("/api/customers", { method: "POST" }) from inside the app's own client component, stop — that's the Server Action's job. The fetch + JSON + handler pattern undoes the type-safety you came for.

Anti-patterns to refuse

  • DB queries in pages or Server Actions. The page is delivery; Prisma is service. If a page has db.customer.findMany, move it to the service.
  • Permission checks in pages or Server Actions. Same reason — easy to forget, security-critical, belongs with the data layer. Pages check requireSession; services check requirePermission.
  • Server Actions that do business logic inline. The action validates and calls; the service does the work. If your action body is >20 lines, the logic belongs in a service.
  • Calling fetch('/api/...') from a client component when a Server Action exists. Server Actions exist for this exact case. Use them.
  • revalidateTag / revalidatePath inside services. These are page-side invalidation primitives (call them from Server Actions, not services). Inside services, use updateTag for tag-keyed invalidation. The distinction matters in Next.js 16 — see references/caching.md.
  • Importing client libraries (React Query, zustand, etc.) into Server Components. They'll crash at build time, but more subtly: they signal that someone is trying to manage server-fetched state in the client when the page should just refetch.
  • use server directive at the top of a page.tsx or component file. That makes EVERY export a Server Action, which is almost never what you want. Server Actions go in dedicated *.actions.ts files in src/server/actions/.

When to break the rules

The rules exist because they pay rent — they make the codebase navigable, secure, and refactorable. Breaking them is allowed when the break itself is the cheaper option, and you're explicit about it.

Examples of legit breaks:

  • A service method that reads but doesn't mutate and is called from a public, unauthenticated route handler (e.g. a supplier portal). The userId-first signature is awkward there — use a sentinel or accept null and document why.
  • A read trivially hot enough to inline in a Server Component (e.g. a header count). Add a comment, move on.
  • A 'use server' action file co-located next to a page (_actions.ts instead of src/server/actions/) when the action is genuinely page-local and won't be reused. Fine, but think about whether reuse will sneak in.

When you break a rule, leave a one-line comment explaining why. Future you, or the next Claude session, needs to know it was intentional.

Workflow — adding a new module

A typical "add a customer module" session looks like:

  1. Prisma model. Add Customer to schema.prisma. Migrate.
  2. Seed permissions. Add customers:read, customers:write to your permission seed, attach to relevant roles.
  3. Service module folder. src/server/modules/customer/.service.ts + .schema.ts. Copy the shape from _example/.
  4. Server Actions. src/server/actions/customer.actions.ts — create / update / delete actions, each one await requireSession() then customerService.<method>(session.user.id, ...).
  5. Pages. src/app/(dashboard)/customers/page.tsx (list), [id]/page.tsx (detail), new/page.tsx (form), [id]/edit/page.tsx (edit form). Each one async, calls the service.
  6. Wire sidebar. Add the Customers entry in src/components/layout/sidebar.tsx.
  7. Tests. customer.service.spec.ts covers the service. See the nfs-testing-patterns skill.
  8. Run the verification gate. pnpm verify — must be green before commit.

The module is a known shape. Don't reinvent it.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.