AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Nextjs Module

skill-deadlymind-nanolama-nextjs-module · by Deadlymind

Structures a Next.js 16 App Router frontend feature as layered modules (lib/api typed fetchers, query-keys, TanStack Query hooks, React 19 pages) fed by one shared fetch client that sends the auth cookie with credentials include, attaches the tenant header, and normalizes errors. Use when scaffolding a new frontend feature or module, building a fetch/apiClient wrapper, wiring cookie-JWT calls to…

No reviews yet
0 installs
22 views
0.0% view→install

Install

$ agentstack add skill-deadlymind-nanolama-nextjs-module

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-deadlymind-nanolama-nextjs-module)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
24d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Nextjs Module? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Next.js module (layered feature + shared fetch client)

When to use

Starting or reorganizing a frontend feature that talks to the DRF backend, or building the fetch client every feature shares. Keep each feature a thin stack of layers so pages stay declarative and the auth/tenant/error concerns live in one place.

Pattern

One feature = four layers, each depending only on the one below it:

app/(pages)  ->  hooks (useQuery/useMutation)  ->  query-keys  ->  lib/api (typed fetchers)
                                                                        |
                                                        shared fetch client (cookie + tenant + errors)
  • lib/api/.ts — typed async fetchers, the only place a URL/verb appears.
  • query-keys — one factory per feature so hooks and invalidation agree on keys.
  • hooksuseQuery/useMutation wrapping the fetchers; components import these.
  • pages — App Router Server/Client Components render hook state, no fetch inline.

Every fetcher goes through one shared client that sends the HttpOnly auth cookie (credentials: "include"), attaches the tenant header, and throws a normalized error. One vertical slice, top of stack unchanged from the client up:

// lib/api/client.ts — one shared client: cookie-JWT, tenant header, normalized errors
export class ApiError extends Error {
  constructor(public status: number, public detail: unknown) { super(`HTTP ${status}`); }
}
const UNSAFE = /^(POST|PUT|PATCH|DELETE)$/i;
const csrfToken = () =>
  decodeURIComponent(document.cookie.match(/(?:^|;\s*)csrftoken=([^;]*)/)?.[1] ?? "");

export async function apiClient(path: string, init: RequestInit = {}): Promise {
  const method = init.method ?? "GET";
  const isForm = init.body instanceof FormData;
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${path}`, {
    ...init,
    credentials: "include",              // HttpOnly cookie rides along; never read the token in JS
    headers: {
      ...(isForm ? {} : { "Content-Type": "application/json" }), // let the browser set the multipart boundary
      ...(UNSAFE.test(method) ? { "X-CSRFToken": csrfToken() } : {}), // cookie auth => CSRF-exposed
      "X-Entreprise": getTenantId(),
      ...init.headers,
    },
  });
  if (!res.ok) throw new ApiError(res.status, await res.json().catch(() => null));
  return res.status === 204 ? (undefined as T) : res.json();   // empty body on 204/DELETE
}

// lib/api/invoices.ts — typed fetchers are the only place a URL/verb appears
export const getInvoices = () => apiClient("/api/invoices/");

// query-keys — one factory so hooks and invalidation agree on keys; tenant is IN the key
export const invoiceKeys = {
  all: (t: string) => ["tenant", t, "invoices"] as const,
  list: (t: string) => [...invoiceKeys.all(t), "list"] as const,
};

// hooks — components import these, never the fetchers
export const useInvoices = () => {
  const tenantId = useUI((s) => s.activeEntrepriseId);
  return useQuery({
    queryKey: invoiceKeys.list(tenantId),   // never share a cache entry across tenants
    queryFn: getInvoices,
    enabled: !!tenantId,
  });
};

// app/invoices/page.tsx — declarative page reads hook state
"use client";
export default function InvoicesPage() {
  const { data, isPending, error } = useInvoices();   // TanStack v5: isPending, not isLoading
  if (isPending) return ;
  if (error) return ;      // ApiError carries .status
  return ;
}

Adapt to your repo

Rename the tenant header (X-Entreprise) and getTenantId() to match your backend and where the id lives (often a Zustand store — see the state pattern). Set NEXT_PUBLIC_API_URL per environment. Pick your folder convention (app/ vs a features/ directory) and hold it repo-wide. If you server-fetch in a Server Component, forward the incoming cookie header explicitly — credentials: "include" only applies to browser fetches.

Gotchas

  • The client's auth contract, in one rule: credentials: "include" on every request,

plus X-CSRFToken on every unsafe method (POST/PUT/PATCH/DELETE). A 403 on a write means that header is missing — never @csrf_exempt the endpoint to silence it. See cookie-auth-csrf for why cookie auth is CSRF-exposed, the cookie flags, credentialed CORS, and refresh/logout.

  • Never hard-code Content-Type: application/json unconditionally. For a FormData

body you must omit the header entirely so the browser can set multipart/form-data with its generated boundary — setting it by hand produces a body the server cannot parse.

  • **Security-critical: the tenant belongs IN the query key, not only in the request

header.** Otherwise two tenants share one cache entry and a tenant switch serves the previous tenant's rows. Scope keys under ["tenant", tenantId, ...] and follow the switch procedure in react-query. Client-side isolation is defence-in-depth, not the boundary — the server still enforces it.

  • A silent 401 that only happens in production is almost never your fetcher — it is the

cookie/CORS seam (cookie-auth-csrf). Check there before editing this layer.

  • Don't call fetch inside components or pages; that scatters auth/tenant/error logic

the client exists to centralize.

  • Return undefined for 204 No Content before calling res.json(), or a DELETE

mutation throws on an empty body.

  • Render loading, empty, AND error as three distinct states, not just success. A

successfully-fetched empty list (data.length === 0) is not an error and not still loading — it needs its own empty-state UI (a message plus a call to action, e.g. a "Create your first invoice" button), never an infinite spinner or a blank table. So the page reads: isPending -> spinner, error -> error state, empty -> empty state, else -> the list.

  • Validate response shapes at the fetcher boundary with Zod when the payload is

untrusted or drifts (see drf-zod-contract).

  • **Security-critical: do authentication and tenant/authorization checks on the

server** — in a Server Component, route handler, or middleware — never in a Client Component. Anything a "use client" component can read is attacker-visible, so client-side gating is UX only, not a boundary (ties rbac-permissions). Keep "use client" at interactive leaves; never put secrets or tenant-authorization logic in client code. For a privileged operation, prefer a Server Action or a server-side fetch over exposing the call (and any check guarding it) to the client.

See also

  • cookie-auth-csrf
  • react-query
  • rbac-permissions
  • zod-forms
  • drf-zod-contract
  • i18n-rtl

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.