標準体系 / tech-stack-2026

The AI-Native 2026 Stack

The verdict. Build on Next.js 16 (App Router) + React 19 + Tailwind v4 + shadcn/ui (Base UI primitives) + TanStack Query + Zustand, deployed on Vercel, generated and edited by v0 grounded in a shadcn registry over MCP. This is not a survey — it is the default. Deviate only for the specific workloads called out below.

This module is decision-oriented. Every choice has a one-line WHY, a current version, and copy-paste setup. The taste layer (color, motion, type) lives in the other modules; here we lock the plumbing so agents stop re-litigating it.


The default stack at a glance

Layer Pick Version (Jun 2026) One-line WHY
UI runtime React 19.2 Actions, use(), RSC, Compiler 1.0 — all stable
Meta-framework Next.js (App Router) 16.2 Most mature RSC; Turbopack default; Cache Components
Styling Tailwind CSS v4.3 CSS-first @theme, OKLCH, runtime CSS vars
Components shadcn/ui (copy-paste) CLI 3.x You own the source — AI edits real code
Primitives Base UI (default) / Radix (existing) 1.x Headless, accessible, full-time maintained
Server-state TanStack Query v5 Suspense-first, smaller, the standard
Client-state Zustand v5 Zero-boilerplate, no Provider, selector subs
Bundler Turbopack (in Next) stable 2–5× builds, ~10× Fast Refresh
Runtime / PM Bun 1.2+ Fastest installs + scripts
Fonts next/font + variable fonts Self-hosted, zero CLS, GDPR-safe
Deploy Vercel (default) / Cloudflare (cost) Fluid Compute vs V8 isolates
AI codegen v0 + shadcn registry + MCP Design-system-grounded generation

One line to memorize: Next 16 + React 19 + Tailwind v4 + shadcn/ui (Base UI) + TanStack Query + Zustand, on Vercel, via v0/MCP.

Two escape hatches you will actually use: content-heavy sites → Astro 6; no-lock-in type-safe full-stack → TanStack Start v1 on Vite 8.


1. React 19 — the foundation

Current: 19.2 (Oct 2025); 19.0 stable Dec 5 2024. React Compiler 1.0 shipped Oct 2025.

WHY it wins: React 19 collapses the useState + useTransition + manual try/catch ritual into Actions, makes data declarative with use(), and ships Server Components + Server Actions as a stable, framework-backed model. The Compiler deletes most hand-written useMemo/useCallback. ref is now a plain prop — forwardRef is gone (codemod available).

Actions — the new form/mutation primitive

"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";

async function updateName(_prev: string, formData: FormData) {
  const name = formData.get("name") as string;
  const res = await fetch("/api/name", { method: "POST", body: name });
  if (!res.ok) return "Failed to update"; // returned value becomes error state
  return ""; // success
}

function SubmitButton() {
  const { pending } = useFormStatus(); // reads the nearest parent <form>
  return (
    <button className="btn-primary" disabled={pending}>
      {pending ? "Saving…" : "Save"}
    </button>
  );
}

export function NameForm() {
  const [error, formAction, isPending] = useActionState(updateName, "");
  return (
    <form action={formAction} aria-busy={isPending}>
      <input name="name" className="input" />
      <SubmitButton />
      {error && <p role="alert" className="text-(--color-danger)">{error}</p>}
    </form>
  );
}

use() — read a promise/context in render

Unlike other hooks, use() may be called conditionally and in loops. Production-stable in Server Components; on the client it integrates with Suspense.

import { use, Suspense } from "react";

function Comments({ commentsPromise }: { commentsPromise: Promise<string[]> }) {
  const comments = use(commentsPromise); // suspends until resolved
  return <ul>{comments.map((c) => <li key={c}>{c}</li>)}</ul>;
}

export default function Page() {
  const commentsPromise = fetch("/api/comments").then((r) => r.json());
  return (
    <Suspense fallback={<p className="text-(--color-fg-muted)">Loading…</p>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

RSC rules you must not get wrong

  • RSC are the default in app/ — zero JS shipped. They cannot use useState/useEffect/context; add "use client" for interactivity.
  • "use server" marks Server Actions (callable from client), not Server Components.
  • Client Components cannot import Server Components (the reverse is fine — pass them as children).
  • RSC is stable as a React feature, but bundler APIs are not semver-stable across 19.x minors — use a framework (Next.js is the most mature). 19.2 adds View Transitions, useEffectEvent, <Activity>.

2. Next.js 16 — the default meta-framework

Current: 16.2 (Mar 18 2026); 16.0 shipped Oct 21 2025. App Router is the path; Pages Router is legacy.

WHY it wins: the most mature RSC implementation, Turbopack is now the default bundler (2–5× builds, up to 10× Fast Refresh), Cache Components make caching explicit and opt-in, and Devtools MCP lets agents diagnose inside the dev loop. create-next-app scaffolds an AGENTS.md.

Cache Components + PPR — the new mental model

Everything is dynamic (request-time) by default; caching is opt-in via "use cache". PPR serves a static shell instantly and streams dynamic content through Suspense.

// next.config.ts — recommended defaults
const nextConfig = {
  cacheComponents: true,   // enables PPR + the "use cache" model
  // reactCompiler: true,  // build-time memoization (Babel-based → slower builds); enable when ready
};
export default nextConfig;
// "use cache" caches a component/function; the compiler derives the cache key
async function ProductList() {
  "use cache";
  const products = await db.products.findMany();
  return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}

Caching APIs (16) — the profile arg is now required

import { revalidateTag, updateTag, refresh } from "next/cache";

revalidateTag("blog-posts", "max"); // SWR; the profile arg is REQUIRED in 16
updateTag(`user-${id}`);            // Server Action: read-your-writes (expire + read fresh, same request)
refresh();                          // Server Action: refresh uncached data only (cache untouched)

proxy.ts (formerly middleware.ts)

// proxy.ts — Node.js runtime; makes the network boundary explicit
import { NextRequest, NextResponse } from "next/server";
export default function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL("/home", request.url));
}

Minimal setup

npx create-next-app@latest    # App Router + TS + Tailwind v4 + ESLint + AGENTS.md by default
# Upgrade an existing app:
npx @next/codemod@canary upgrade latest

Breaking changes you will hit (16)

Change Action
Node 18 dropped Require Node 20.9+, TS 5.1+, Safari 16.4+/Chrome 111+
params/searchParams/cookies()/headers()/draftMode() async await them everywhere
experimental.ppr / dynamicIO removed Use cacheComponents: true
next lint removed Move to ESLint flat config or Biome
images.domains deprecated Use images.remotePatterns
Want webpack next build --webpack to opt out of Turbopack

AI-native angle: Next Devtools MCP gives agents routing/caching/rendering knowledge, unified browser+server logs, automatic stack-trace access, and active-route awareness — so the agent fixes the bug inside the dev loop instead of guessing.


3. Framework alternatives — pick by workload

Do not reach for these by default. Reach for them when the workload column describes your app exactly.

Framework Version Pick it when… The reason
Next.js 16 16.2 App-first product, RSC, edge SSR Most mature RSC; deepest Vercel integration
Astro 6 stable (Mar 2026) Content / marketing / docs / blog Ships ~zero JS; Content Layer; Server Islands
TanStack Start v1 v1 (GA late 2025) Type-safe full-stack, no Vercel lock-in End-to-end types; server functions; Vite + Nitro
React Router v7 v7 Migrating off Remix Remix folded in; v6→v7 non-breaking
SvelteKit + Svelte 5 SK 2.57 / Svelte 5.55 Smallest bundles, lean apps Runes reactivity; single-command deploy

Astro 6 — content-driven sites

Content Layer is a unified, type-safe loader API (Markdown, CMS, REST, assets). Markdown builds up to 5× faster, MDX up to , memory cut 25–50%. Server Islands combine static HTML with deferred dynamic components via server:defer, and React/Vue/Svelte/Solid islands can coexist on one page.

// src/content.config.ts (v6 path; was src/content/config.ts in v5)
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";

const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/blog" }),
  schema: z.object({ title: z.string(), date: z.date() }),
});
export const collections = { blog };
<!-- Server Island: skipped on first paint, streamed in later -->
<UserAvatar server:defer>
  <Placeholder slot="fallback" />
</UserAvatar>

Migration (v5→v6): Node ^22.13.0 || ^24, Zod major bump (.nonempty().min(1)), removed Astro.glob and getEntryBySlug.

TanStack Start v1 — type-safe, no lock-in

Built on TanStack Router + Vite + Nitro. Server functions are TypeScript functions that always run on the server but are called like normal functions from the client — no hand-written API contracts. Pairs natively with TanStack Query/Router/Table/Form. RSC is experimental in v1 — choose Next.js if you need RSC today.

import { createServerFn } from "@tanstack/react-start";

const getUser = createServerFn({ method: "GET" })
  .handler(async () => db.user.findFirst()); // runs only on the server

SvelteKit + Svelte 5 runes

Four runes replace implicit reactivity and now work outside .svelte files (in .svelte.ts). Adopt file-by-file — runes mode activates the moment you use any rune.

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
  $effect(() => console.log("count is", count)); // runs after DOM update
</script>
<button onclick={() => count++}>{count} / {doubled}</button>

Scaffold with the sv CLI; $app/stores is deprecated in favor of $app/state. React Router v7 is the Remix successor (single react-router package; framework mode adds Vite compiler, SSR, type-safe loaders/actions, HMR).


4. Tailwind CSS v4 — styling that doubles as design tokens

Current: v4.3 (v4.0 early 2025). New Oxide engine (Rust + Lightning CSS). No JS config — all customization is CSS-first via @theme.

WHY it wins: every design token becomes a real CSS variable at runtime, so themes override without a rebuild — the styling layer is the token layer. The palette is OKLCH for a wider gamut and perceptually-uniform lightness. Models are heavily trained on its utility vocabulary, and v0/shadcn emit it natively.

Setup (Vite path; Next ships it via create-next-app)

bun add tailwindcss @tailwindcss/vite
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
export default { plugins: [tailwindcss()] };
/* app.css — single import; no @tailwind base/components/utilities */
@import "tailwindcss";

@theme — wire the canonical token contract

The design system's framework-agnostic CSS variables are mirrored here so Tailwind generates utilities for them. Define semantic tokens once; theming cascades.

@import "tailwindcss";

@theme {
  /* Color — semantic, OKLCH(L C H). These create bg-*, text-*, border-* utilities */
  --color-bg: oklch(0.99 0.003 240);
  --color-bg-subtle: oklch(0.97 0.005 240);
  --color-surface: oklch(1 0 0);
  --color-surface-2: oklch(0.98 0.004 240);
  --color-fg: oklch(0.21 0.01 240);
  --color-fg-muted: oklch(0.50 0.01 240);
  --color-fg-subtle: oklch(0.65 0.008 240);
  --color-border: oklch(0.91 0.006 240);
  --color-border-subtle: oklch(0.94 0.004 240);
  --color-accent: oklch(0.58 0.18 256);
  --color-accent-fg: oklch(0.99 0.005 256);
  --color-accent-hover: oklch(0.52 0.19 256);
  --color-success: oklch(0.62 0.15 150);
  --color-warning: oklch(0.75 0.15 75);
  --color-danger: oklch(0.58 0.21 25);
  --color-ring: oklch(0.58 0.18 256);

  /* Space — 8pt-based */
  --spacing: 0.25rem;            /* p-4 = calc(var(--spacing) * 4) = 1rem */

  /* Radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
  --radius-xl: 0.75rem;
  --radius-2xl: 1rem;

  /* Type — fluid clamp() + variable font */
  --font-body: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
  --font-mono: "Berkeley Mono", ui-monospace, monospace;
  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.0625rem);
  --text-3xl: clamp(1.875rem, 1.5rem + 1.5vw, 2.5rem);

  /* Elevation — low-opacity layered shadows over hairline borders */
  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.05);
  --shadow-md: 0 2px 4px oklch(0 0 0 / 0.05), 0 4px 12px oklch(0 0 0 / 0.06);

  /* Motion */
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-spring: linear(0, 0.4 9%, 0.9 25%, 1.05 45%, 1 60%, 1);
}

Dark mode — adjust L & C, never naive invert

/* Plain @theme lets a parent selector override the variable — that is the cascade */
[data-theme="dark"] {
  --color-bg: oklch(0.17 0.01 240);
  --color-surface: oklch(0.21 0.012 240);
  --color-fg: oklch(0.96 0.005 240);
  --color-fg-muted: oklch(0.70 0.01 240);
  --color-border: oklch(0.30 0.01 240);
  --color-accent: oklch(0.68 0.16 256); /* lift L, ease C for dark surfaces */
}
  • Use plain @theme (cascades); use @theme inline only when the utility should NOT participate in the cascade.
  • Clear a namespace with --color-*: initial; before redefining a custom palette.
  • Gradient interpolation: bg-linear-to-r/oklch vs /srgb (oklab is default).
  • Browser floor (OKLCH default): Safari 16.4+, Chrome 111+, Firefox 128+.

When to swap styling engine

Alternative Pick it when…
CSS Modules Zero-runtime, locally-scoped plain CSS; incremental migration off legacy
vanilla-extract TypeScript-authored, type-safe zero-runtime tokens (createTheme/contracts)
Tailwind v4 (default) The AI-native choice — models + v0 + shadcn all speak it

5. Component layers — own your code

Modern UI is layered: headless accessibility primitivesshadcn/ui copy-paste components styled with your themed tokens → your design system. Never ship shadcn/Tailwind defaults raw — theme them.

Layer Library Current Role
Copy-paste components shadcn/ui CLI 3.x You own the source; not an npm dependency
Headless primitives Base UI (default) 1.x (Dec 2025) Unstyled, accessible; render-prop composition
Headless primitives Radix UI mature Original; asChild; ~131M weekly downloads
Accessibility hooks React Aria (Adobe) latest Hooks-first; deepest a11y + i18n; most verbose

shadcn/ui — the AI-native distribution model

Not a library — a code-distribution system. npx shadcn add button copies source into your repo, so you own and customize it. Upstream changes are not auto-pulled (re-run add or merge).

npx shadcn@latest init                       # writes components.json
npx shadcn@latest add button card dialog form

CLI 3.0 (Aug 2025) added namespaced registries and a rewritten MCP server (zero-config, works with any registry):

// components.json — mix registries; deps auto-resolve
{
  "registries": {
    "@acme": "https://registry.acme.com/{name}.json",
    "@v0": "https://v0.dev/r/{name}.json"
  }
}
npx shadcn@latest add @acme/button @v0/dashboard

In Dec 2025 shadcn added Base UI as a selectable primitive layer alongside Radix — new projects pick either; existing Radix projects need no migration.

Base UI vs Radix — the call

  • Base UI (creators of Radix, Floating UI, Material UI) hit 1.0 in Dec 2025, full-time MUI-backed. Single dep @base-ui/react. Broader coverage (multi-select, combobox), render-prop composition instead of asChild.
  • Radix pioneered headless React; still huge and reliable, momentum slowed post-WorkOS acquisition.
  • Verdict: new projects → Base UI; existing Radix-based shadcn apps → stay, no rush.
// Base UI — explicit render prop instead of Radix's asChild. Themed with token vars.
import { Dialog } from "@base-ui/react/dialog";

export function ConfirmDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={<button className="btn-primary" />}>Delete</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Backdrop className="fixed inset-0 bg-black/40 backdrop-blur-sm" />
        <Dialog.Popup className="fixed inset-1/2 -translate-1/2 rounded-(--radius-xl) bg-(--color-surface) p-6 shadow-md ring-1 ring-(--color-border)">
          <Dialog.Title className="text-(--color-fg) font-(--font-display)">Delete item?</Dialog.Title>
          <Dialog.Description className="text-(--color-fg-muted)">This cannot be undone.</Dialog.Description>
        </Dialog.Popup>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

React Aria — reach for it when accessibility is contractual (government/enterprise WAI-ARIA). Hooks (useButton, useDialog, useTabs) cover 40+ patterns with rigorous ARIA, i18n, RTL, locale-aware dates/numbers — more code, maximum control. Skip it for fast v0-style prototyping.


6. State & data — split the two axes

Server-state (async data from a backend) and client-state (UI/ephemeral) are different problems. Use the right tool for each.

TanStack Query v5 — server-state

~20% smaller than v4; single-object API; Suspense first-class (useSuspenseQuery). Renames: cacheTimegcTime, keepPreviousDataplaceholderData, loadingpending. Ships a codemod + ESLint rule. Always design loading / error / empty.

import { useQuery } from "@tanstack/react-query";

function Todos() {
  const { data, isPending, error } = useQuery({
    queryKey: ["todos"],
    queryFn: () => fetch("/api/todos").then((r) => r.json()),
  });

  if (isPending) return <TodosSkeleton />;                              // loading
  if (error) return <p role="alert" className="text-(--color-danger)">Couldn’t load todos.</p>; // error
  if (data.length === 0) return <EmptyState label="No todos yet" />;    // empty

  return <ul>{data.map((t) => <li key={t.id}>{t.title}</li>)}</ul>;
}

In Next App Router: fetch initial data in Server Components / Server Actions; use TanStack Query for client-side caching, refetching, infinite lists, and mutations.

Zustand v5 — client-state

User-satisfaction leader in State of React 2025. Zero-boilerplate create(), no Provider, selector subscriptions, useSyncExternalStore under the hood. Always update immutably — new objects, never mutation.

import { create } from "zustand";
import { persist } from "zustand/middleware";

type CartState = { items: string[]; add: (id: string) => void };

export const useCart = create<CartState>()(
  persist(
    (set) => ({
      items: [],
      add: (id) => set((s) => ({ items: [...s.items, id] })), // new array, no mutation
    }),
    { name: "cart" }
  )
);

// subscribe to a slice only — avoids needless re-renders
const count = useCart((s) => s.items.length);

Alternatives: Redux Toolkit for large, long-lived apps needing strict structure/debuggability; Jotai for atomic/derived graphs. For most UI-heavy apps, Zustand + TanStack Query covers both axes with minimal ceremony.


7. Build tooling — Rust everywhere

The 2026 theme: core JS tooling is rewritten in Rust/Zig. You rarely choose this directly — the framework picks it — but know the map.

Tool Current Use for Notes
Turbopack stable in Next 16 Next.js apps Default bundler; Next-locked (no standalone yet)
Vite 8 + Rolldown v8 (Rolldown RC) Everything non-Next Rolldown (Rust) replaces esbuild + Rollup
Rspack stable webpack migrations Fastest cold start/prod; keeps webpack config
Bun 1.2+ installs, scripts, server bundles Zig runtime; fastest installs; complements Vite

Guidance: Rspack for migrations, Turbopack for Next.js shops, Vite for everything else. Run Vite with Bun (Bun for installs/scripts/runtime, Vite for HMR + framework plugins + prod build) — they complement, not compete. Treat vendor "10–30×" numbers as best-case; independent benchmarks show HMR roughly comparable between Turbopack and Vite+Rolldown.

# Minimal Vite 8 + React + Tailwind, driven by Bun
bun create vite@latest my-app -- --template react-ts
cd my-app && bun add tailwindcss @tailwindcss/vite
bun install && bun run dev

8. Fonts — self-hosted variable fonts

Best practice: self-host (privacy + GDPR + speed), prefer variable fonts (one file, all weights), expose a CSS variable so Tailwind/CSS consumes it. next/font preloads at build and uses size-adjust fallbacks to kill CLS. Per the taste layer: do not ship Inter/Roboto/system as the only font — pick a distinctive --font-display and pair it with a clean body face.

// app/layout.tsx — auto self-hosted; zero runtime request to Google
import { Geist } from "next/font/google";
import localFont from "next/font/local";

const body = Geist({ subsets: ["latin"], variable: "--font-body" });
const display = localFont({ src: "./fonts/Hubot.woff2", variable: "--font-display" });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={`${body.variable} ${display.variable}`}>
      <body>{children}</body>
    </html>
  );
}
/* Tailwind v4 — wire the CSS vars into the theme */
@theme {
  --font-body: var(--font-body), ui-sans-serif, system-ui, sans-serif;
  --font-display: var(--font-display), ui-sans-serif, system-ui;
}

Fontsource (framework-agnostic, version-locked) — fonts as npm dependencies with auto fallbacks. Use for non-Next setups (Vite/Astro/SvelteKit) or when you want fonts pinned in package.json:

import "@fontsource-variable/inter"; // self-hosted, no external request

9. Deployment — Vercel vs Cloudflare

Platform Pick it for Model Cost shape
Vercel Next.js SSR, zero-config Fluid Compute (warm Node, near-zero cold start, 2 vCPU/4GB) Usage-based Pro; pricier at high traffic
Cloudflare Workers Edge logic, global, cost V8 isolates, 330+ cities, sub-5ms cold starts, full storage (KV/R2/D1/DO) ~$5/mo for 10M req, no egress fees
  • Vercel is the default — deepest Next integration; Fluid Compute is ~1.2–5× faster SSR than Workers for render-heavy apps. Lock-in is framework-shaped (ISR/on-demand revalidation lean on Vercel infra).
  • Cloudflare Workers wins on cost + cold-start distribution. Run Next via OpenNext (@opennextjs/cloudflare, Node.js runtime, supports Next 14/15/16). Lock-in is storage-shaped (Durable Objects non-portable; R2 is S3-compatible, D1 is SQLite).
  • Hybrid is the 2026 winner: auth at the edge, SSR on Fluid Compute, CPU-heavy jobs on serverless.
# Vercel (default)
npx vercel deploy

# Cloudflare via OpenNext (cost-sensitive global edge)
bun add @opennextjs/cloudflare
npx @opennextjs/cloudflare && npx wrangler deploy   # auto-detects Next config

10. The AI-native angle — why this exact stack

The stack is chosen for what models generate well and what they can be grounded on. This is the load-bearing reason the defaults are not negotiable.

Why models love it

  • shadcn/ui + Tailwind + Base UI/Radix are the patterns models are most trained on. v0 uses shadcn/ui by default — every extra abstraction layer (wrapper components, overridden styles, mismatched APIs) makes output harder to predict. Owning the source lets models edit real code.
  • Copy-paste, not npm: generated code drops into a repo that already owns identical component source — no version drift, no wrapper guessing.

v0 (Vercel)

AI UI generator on Vercel's frontend-tuned model (React + Tailwind + shadcn/ui). Rebranded v0.dev → v0.app (Jan 2026); 6M+ developers (Mar 2026). 2026 added a sandbox runtime, a Git panel (branches/PRs from chat), DB integrations, and agentic workflows; it generates multi-page Next apps. Caveat: frontend-focused; one-click deploy is Vercel-only.

Registry + MCP = grounding (the design-tokens bridge)

A shadcn registry is a distribution spec that passes your design system context (components, blocks, tokens) to AI models. It supports MCP, so the same registry grounds v0, Cursor, Windsurf, and Claude Code — any MCP-ready tool — in your real components and tokens. The token contract is the bridge between a human-maintained design system and AI codegen.

// registry.json — host at /r/registry.json (root) for MCP discovery
{
  "name": "acme-ui",
  "homepage": "https://registry.acme.com",
  "items": [
    {
      "name": "button",
      "type": "registry:component",
      "files": [{ "path": "ui/button.tsx", "type": "registry:component" }],
      "registryDependencies": ["@shadcn/button"]
    }
  ]
}
# Inspect the registry MCP server inside Claude Code
/mcp

AI-consumable registry rules: accurate dependency lists, registryDependencies for relationships, kebab-case names, concise descriptions so agents understand each item. The Registry Starter (Next + shadcn) ships an "Open in v0" button that deep-links /r/<name>.json into a v0 chat.

Security: public/community MCP registries are a supply-chain target — review installed code; prefer private/internal registries for proprietary systems.


11. The full minimal setup (copy-paste)

# 1. Scaffold (App Router + TS + Tailwind v4 + ESLint + AGENTS.md)
npx create-next-app@latest acme-app
cd acme-app

# 2. Components — shadcn/ui (choose Base UI primitives when prompted)
npx shadcn@latest init
npx shadcn@latest add button card dialog form

# 3. Data + client state
bun add @tanstack/react-query zustand

# 4. Enable the new caching model -> set cacheComponents: true in next.config.ts

# 5. Deploy
npx vercel deploy
// next.config.ts — recommended defaults
const nextConfig = {
  cacheComponents: true,           // PPR + "use cache"
  // reactCompiler: true,          // enable once build-time cost is acceptable
};
export default nextConfig;

Decision shortcuts

If you are building… Then ship…
App-first product, RSC Next 16 + the default stack (don't overthink it)
Marketing / docs / blog Astro 6 + Tailwind v4 + island components
No-lock-in type-safe full-stack TanStack Start v1 + TanStack Query on Vite 8
Migrating off Remix React Router v7 framework mode
Cost-sensitive global edge Next via OpenNext on Cloudflare Workers
Contractual accessibility React Aria at the primitive layer
Smallest possible bundle SvelteKit + Svelte 5 runes

Sources

原文ファイル: design-system/tech-stack-2026/stack.md