[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D2G4JTC1E6E1YJ8FCM05PF":3,"skill-same:01M6D2G4JTC1E6E1YJ8FCM05PF":67},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":47,"repositoryHtmlUrl":48,"repositoryHomepageUrl":49,"ownerHtmlUrl":50,"ownerBlogUrl":49,"ownerDescription":51,"updatedAt":52,"install":53,"resources":66},"01M6D2G4JTC1E6E1YJ8FCM05PF","ui-stack-next",null,"Building interfaces with Next.js (App Router, 15 and 16): project layout, route groups, parallel and intercepting routes, server components with small client leaves and serialisable props, data fetching and the two caching models (fetch options, cache, use cache and Cache Components, dynamic triggers, Partial Prerendering), server actions with useActionState, loading, error and not-found files, streaming with Suspense, metadata, next\u002Ffont, next\u002Fimage, next\u002Flink, proxy and middleware redirects, public and server environment variables, reading next build, and Next's own traps. Read when the project uses Next.js.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-next\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"ui stack next","ui","stack","next","fetch","layout","project","caching","options","route","files","router","image","read","parallel","data","actions","server","streaming","components","2026-11-10 10:12:30",false,"---\nname: ui-stack-next\ndescription: \"Building interfaces with Next.js (App Router, 15 and 16): project layout, route groups, parallel and intercepting routes, server components with small client leaves and serialisable props, data fetching and the two caching models (fetch options, cache, use cache and Cache Components, dynamic triggers, Partial Prerendering), server actions with useActionState, loading, error and not-found files, streaming with Suspense, metadata, next\u002Ffont, next\u002Fimage, next\u002Flink, proxy and middleware redirects, public and server environment variables, reading next build, and Next's own traps. Read when the project uses Next.js.\"\n---\n\n# Next.js (App Router)\n\nThe generated Next.js app puts `\"use client\"` at the top of every page,\nfetches its own `\u002Fapi` routes in `useEffect` behind a spinner, keeps the\nstarter's font, SVGs and favicon, and grows its client bundle with every\nimport. React rules apply (`ui-stack-react`) and the server side is in\n`backend-stack-next`; these are Next's own: where the client boundary goes,\nhow data and caching work, the special files, and the built-ins.\n\n## 1. Know the version\n\nRead `next` in `package.json` first, and an API's page in the docs that ship\nin `node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F` (16.2+) rather than recalling it. 16 is\ncurrent (16.3 in September 2026) and changed what you will meet:\n\n- `middleware.ts` became `proxy.ts` (exporting `proxy`, on Node.js);\n  `middleware.ts` still works, deprecated, for the edge runtime.\n- `params`, `searchParams`, `cookies()` and `headers()` are async only.\n- `next build` no longer lints and `next lint` is gone; Turbopack is the\n  default, and a custom `webpack` config fails the build until migrated.\n- Cache Components (`cacheComponents: true`) replace the PPR and\n  `dynamicIO` flags; `revalidateTag` takes a second argument.\n- `next\u002Fimage`: `preload` replaces `priority`; quality 75 only, unless\n  `images.qualities` lists more. Parallel route slots need `default.tsx`.\n\n## 2. Project layout\n\n```text\napp\u002Flayout.tsx              \u003Chtml lang>, fonts, providers\napp\u002Fglobals.css             tokens and base styles, imported once, here\napp\u002F(marketing)\u002Flayout.tsx  the site frame: header, footer\napp\u002F(app)\u002Flayout.tsx        the app shell: sidebar, top bar\napp\u002F(app)\u002Forders\u002Fpage.tsx   loading.tsx and error.tsx beside it\ncomponents\u002Fui\u002F  components\u002Forders\u002F   primitives, then feature parts\nserver\u002F                     data access, `import \"server-only\"` in each\nproxy.ts                    middleware.ts before 16\n```\n\n- `app\u002Flayout.tsx` holds `\u003Chtml lang=\"...\">`, the fonts, and the header and\n  footer every page shares. Route groups give different frames without\n  appearing in the URL; a `_components` folder is private, never a route.\n- Parallel routes (`@panel`) for panes that load and fail on their own;\n  intercepting routes (`@modal\u002F(.)photos\u002F[id]`) for a record opened as a\n  modal over its list with its own URL. Only where that URL behaviour is\n  wanted; a dialog's open state is simpler.\n- Every link in the navigation points at a route that exists.\n\n## 3. Server by default, small client leaves\n\n- Components are server components by default. `\"use client\"` goes on the\n  small interactive leaves (a menu button, a form, a chart), never at the\n  top of a page or layout: everything a client file imports ships.\n- Props into a client component are serialisable: plain objects, arrays,\n  strings, numbers, booleans, `Date`, `Map`, `Set`, promises, server\n  actions. Not functions, class instances or database rows: pass the fields\n  shown (`backend-stack-next`). Server content goes in as `children`:\n\n```tsx\nexport default async function OrdersPage() {\n  const orders = await listOrders();  \u002F\u002F the server-only data layer\n  return (\n    \u003CFilterPanel>                    {\u002F* \"use client\": holds its open state *\u002F}\n      \u003COrderTable orders={orders} \u002F> {\u002F* still rendered on the server *\u002F}\n    \u003C\u002FFilterPanel>\n  );\n}\n```\n\n- `import \"server-only\"` in modules holding queries or secrets,\n  `import \"client-only\"` in modules touching `window`: a leak becomes a\n  build error.\n\n## 4. Data and caching\n\n- Fetch in server components, close to where the data is shown, through\n  the data layer. Never `useEffect` with `fetch(\"\u002Fapi\u002F...\")` for a page's\n  first data, and never a page calling its own route handler.\n- Start independent reads together (`Promise.all`), or give each slow part\n  its own `\u003CSuspense>` so it streams without holding the rest. `cache()`\n  from React dedupes a call within one request.\n\nWhich caching model runs is in `next.config.ts`:\n\n| | Cache Components (`cacheComponents: true`, 16) | Previous model (15, or the flag off) |\n|---|---|---|\n| Default | Nothing cached; each route prerenders a static shell and the rest streams in (Partial Prerendering) | `fetch` not cached; a route is static unless it reads request data |\n| To cache | `\"use cache\"` at the top of the function or component, with `cacheLife(\"hours\")` and `cacheTag(\"orders\")` | `fetch(url, { next: { revalidate: 3600, tags: [\"orders\"] } })`, `unstable_cache` for other reads, `export const revalidate` |\n| `cookies()`, `headers()`, `searchParams` | Read inside `\u003CSuspense>`; only that part waits | The whole route becomes dynamic |\n| `Date.now()`, random ids | After `await connection()`, inside Suspense | A dynamic route, or `export const dynamic = \"force-dynamic\"` |\n\nArguments and captured values become part of a `\"use cache\"` key: never\ncache one user's data under a shared key. Say in the report what is cached,\nfor how long, and what is dynamic.\n\n## 5. Mutations: server actions and forms\n\nServer actions (`\"use server\"`) for the app's own forms and buttons; route\nhandlers for webhooks, other clients and files (`backend-stack-next`).\nEvery action validates its input and checks the user.\n\n```ts\n\"use server\"; \u002F\u002F app\u002F(app)\u002Forders\u002Factions.ts\nimport { z } from \"zod\";\nimport { updateTag } from \"next\u002Fcache\";\nimport { redirect } from \"next\u002Fnavigation\";\nimport { insertOrder, requireUser } from \"@\u002Fserver\u002Forders\";\nconst Order = z.object({ title: z.string().trim().min(1, \"Give the order a title.\") });\nexport type OrderState = { errors?: { title?: string[] }; title?: string };\nexport async function createOrder(_: OrderState, data: FormData): Promise\u003COrderState> {\n  const user = await requireUser();\n  const parsed = Order.safeParse({ title: data.get(\"title\") });\n  if (!parsed.success) {\n    return { errors: z.flattenError(parsed.error).fieldErrors, title: String(data.get(\"title\") ?? \"\") };\n  }\n  const order = await insertOrder(user, parsed.data);\n  updateTag(\"orders\"); \u002F\u002F revalidatePath(\"\u002Forders\") before 16\n  redirect(`\u002Forders\u002F${order.id}`);\n}\n```\n\n```tsx\n\"use client\"; \u002F\u002F app\u002F(app)\u002Forders\u002Fnew\u002Forder-form.tsx\nimport { useActionState } from \"react\";\nimport { createOrder } from \"..\u002Factions\";\nexport function OrderForm() {\n  const [state, action, pending] = useActionState(createOrder, {});\n  return (\n    \u003Cform action={action}>\n      \u003Clabel htmlFor=\"title\">Title\u003C\u002Flabel>\n      \u003Cinput id=\"title\" name=\"title\" defaultValue={state.title}\n        aria-invalid={!!state.errors?.title} aria-describedby=\"title-error\" \u002F>\n      \u003Cp id=\"title-error\">{state.errors?.title?.[0]}\u003C\u002Fp>\n      \u003Cbutton type=\"submit\" disabled={pending}>{pending ? \"Creating...\" : \"Create order\"}\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  );\n}\n```\n\n- `redirect()` and `notFound()` throw: call them outside `try`\u002F`catch`.\n- After a write: `updateTag(tag)` in an action (16) shows the user their\n  change at once; elsewhere, `revalidateTag(tag, \"max\")` or `revalidatePath`.\n- Pending: `useActionState`'s third value, or `useFormStatus` in a child of\n  the form; `useOptimistic` for a list that changes before the server\n  answers. React resets an uncontrolled form after its action: on a\n  failure, return the values and feed them back as `defaultValue`.\n- A form that only navigates (search, filters) is `\u003CForm action=\"\u002Fsearch\">`\n  from `next\u002Fform`: the query lands in the URL and the route is prefetched.\n\n## 6. Loading, errors and not found\n\n- `loading.tsx` in each segment that loads data: a skeleton of the real\n  layout (the same header and columns), not a centred spinner.\n- `error.tsx` (`\"use client\"`) says what failed in words and offers a retry\n  (`retry()` from 16.3, `reset()` before); it does not cover its own\n  segment's layout. `global-error.tsx` covers the root layout with its own\n  `\u003Chtml>` and `\u003Cbody>`, without the global styles or the theme.\n- `notFound()` renders the nearest `not-found.tsx`; `app\u002Fnot-found.tsx`\n  offers a way back. Expected failures are returned and shown, not thrown\n  (`frontend-errors`); `catchError` from `next\u002Ferror` (16.3) puts a\n  boundary with a retry around one part of a page.\n\n## 7. Metadata, fonts, images, links\n\n```tsx\n\u002F\u002F app\u002Flayout.tsx; the root element: \u003Chtml lang=\"en\" className={`${display.variable} ${sans.variable}`}>\nimport type { Metadata } from \"next\";\nimport { Newsreader, Public_Sans } from \"next\u002Ffont\u002Fgoogle\"; \u002F\u002F the faces DESIGN.md names\nconst display = Newsreader({ subsets: [\"latin\"], variable: \"--font-newsreader\" });\nconst sans = Public_Sans({ subsets: [\"latin\"], variable: \"--font-public-sans\" });\nexport const metadata: Metadata = {\n  metadataBase: new URL(\"https:\u002F\u002F[domain]\"),\n  title: { default: \"[Product]\", template: \"%s · [Product]\" },\n  description: \"[What it does, in one sentence]\",\n};\n```\n\n- A title and description per page: `export const metadata`, or\n  `generateMetadata` when they depend on data (`params` is a promise; share\n  the page's read through `cache()`). `opengraph-image.tsx`, `icon.svg`,\n  `sitemap.ts` and `robots.ts` are files in `app\u002F` (`frontend-seo`).\n- Fonts only through `next\u002Ffont` (`google` or `local`), self-hosted at\n  build and exposed as CSS variables; never a `\u003Clink>` or `@import` to\n  Google Fonts. Map them under another name (`@theme inline { --font-display:\n  var(--font-newsreader); }`): a variable set to itself is invalid.\n- Images with `next\u002Fimage`: `width` and `height`, or `fill` with `sizes` in\n  a positioned parent; `sizes` that match the layout (`\"(min-width: 1024px)\n  50vw, 100vw\"`); `preload` (`priority` before 16) or `fetchPriority=\"high\"`\n  on the one image that is the largest thing on first view, and on no\n  other; remote hosts in `images.remotePatterns` (`ui-part-images`).\n- Internal links with `next\u002Flink`; they prefetch in production as they\n  scroll into view. `prefetch={false}` on long lists of row links;\n  `useLinkStatus()` inside a link for a pending hint on a slow route.\n\n## 8. Proxy, and environment variables\n\n```ts\n\u002F\u002F proxy.ts (16); before it, middleware.ts exporting `middleware`\nimport { NextResponse, type NextRequest } from \"next\u002Fserver\";\nexport function proxy(request: NextRequest) {\n  if (request.cookies.has(\"session\")) return NextResponse.next();\n  const login = new URL(\"\u002Flogin\", request.url);\n  login.searchParams.set(\"next\", request.nextUrl.pathname);\n  return NextResponse.redirect(login);\n}\nexport const config = { matcher: [\"\u002Fapp\u002F:path*\", \"\u002Fsettings\u002F:path*\"] };\n```\n\n- An optimistic redirect, not authorisation: the data layer checks the\n  user on every read and write (`backend-auth`). `middleware.ts` runs on\n  the edge runtime: no Node APIs, no database driver. The sign-in page\n  accepts only a relative `next`, or it is an open redirect.\n- `NEXT_PUBLIC_` variables are compiled into the browser bundle at build:\n  never a key, and a change needs a rebuild. Server values have no prefix\n  and are read in server-only modules; `.env.example` lists the names.\n\n## 9. Styling and traps\n\n- Styling: the project's method (Tailwind, `ui-stack-tailwind` and\n  `ui-stack-shadcn`; CSS Modules; or tokens in `app\u002Fglobals.css`), tokens\n  once. Themes: `next-themes` with `suppressHydrationWarning` (`ui-themes`).\n- Hydration mismatches: dates formatted in the server's time zone or locale\n  (pass `timeZone` and the locale, or format after mount), `Math.random()`\n  or `Date.now()` in render (`useId` for ids), `typeof window` branches, a\n  `\u003Cdiv>` in a `\u003Cp>`. `suppressHydrationWarning` only where it is meant.\n- One import pulling a whole library into a client file (charts, dates, a\n  barrel of icons): import the one function, load heavy client-only\n  widgets with `next\u002Fdynamic`, list barrels in\n  `experimental.optimizePackageImports`.\n- `searchParams` read in a layout (layouts do not get them), `params` read\n  without `await`, `cookies().set()` in a server component (only actions\n  and route handlers set cookies).\n\n## Check it\n\n- `npm run build` type-checks and prerenders every route; fix what it\n  reports. Its table marks each route static (○), prerendered with params\n  (●), partially prerendered (◐) or dynamic (ƒ): a page meant to be static\n  that shows ƒ reads request data somewhere. Next 15 prints First Load JS\n  per route; in 16, `npx next experimental-analyze` (16.1+).\n- `npm run lint` for lint (in 16 the build no longer runs it).\n- Look at the result with the `preview` tool: `url` `http:\u002F\u002Flocalhost:3000\u002F`\n  and `start` `npm run dev -- -p 3000`; it starts the server, waits for it\n  and stops it afterwards. Caching and streaming differ in dev: check them\n  against `npm run build && npm run start`, and in the page source.\n\n## Avoid\n\n`\"use client\"` at the top of pages; `useEffect` for a page's first data; a\npage fetching its own API route; a client component importing the database\nclient; fonts from a Google `\u003Clink>`; `\u003Cimg>` for content images; `preload`\non several images; auth only in proxy or middleware; a key in a\n`NEXT_PUBLIC_` variable; one user's data in a shared cache; `redirect()`\ninside `try`; a spinner where `loading.tsx` could show the layout; the\nstarter's SVGs, font and favicon left in place.\n","# Next.js (App Router)\n\nThe generated Next.js app puts `\"use client\"` at the top of every page,\nfetches its own `\u002Fapi` routes in `useEffect` behind a spinner, keeps the\nstarter's font, SVGs and favicon, and grows its client bundle with every\nimport. React rules apply (`ui-stack-react`) and the server side is in\n`backend-stack-next`; these are Next's own: where the client boundary goes,\nhow data and caching work, the special files, and the built-ins.\n\n## 1. Know the version\n\nRead `next` in `package.json` first, and an API's page in the docs that ship\nin `node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F` (16.2+) rather than recalling it. 16 is\ncurrent (16.3 in September 2026) and changed what you will meet:\n\n- `middleware.ts` became `proxy.ts` (exporting `proxy`, on Node.js);\n  `middleware.ts` still works, deprecated, for the edge runtime.\n- `params`, `searchParams`, `cookies()` and `headers()` are async only.\n- `next build` no longer lints and `next lint` is gone; Turbopack is the\n  default, and a custom `webpack` config fails the build until migrated.\n- Cache Components (`cacheComponents: true`) replace the PPR and\n  `dynamicIO` flags; `revalidateTag` takes a second argument.\n- `next\u002Fimage`: `preload` replaces `priority`; quality 75 only, unless\n  `images.qualities` lists more. Parallel route slots need `default.tsx`.\n\n## 2. Project layout\n\n```text\napp\u002Flayout.tsx              \u003Chtml lang>, fonts, providers\napp\u002Fglobals.css             tokens and base styles, imported once, here\napp\u002F(marketing)\u002Flayout.tsx  the site frame: header, footer\napp\u002F(app)\u002Flayout.tsx        the app shell: sidebar, top bar\napp\u002F(app)\u002Forders\u002Fpage.tsx   loading.tsx and error.tsx beside it\ncomponents\u002Fui\u002F  components\u002Forders\u002F   primitives, then feature parts\nserver\u002F                     data access, `import \"server-only\"` in each\nproxy.ts                    middleware.ts before 16\n```\n\n- `app\u002Flayout.tsx` holds `\u003Chtml lang=\"...\">`, the fonts, and the header and\n  footer every page shares. Route groups give different frames without\n  appearing in the URL; a `_components` folder is private, never a route.\n- Parallel routes (`@panel`) for panes that load and fail on their own;\n  intercepting routes (`@modal\u002F(.)photos\u002F[id]`) for a record opened as a\n  modal over its list with its own URL. Only where that URL behaviour is\n  wanted; a dialog's open state is simpler.\n- Every link in the navigation points at a route that exists.\n\n## 3. Server by default, small client leaves\n\n- Components are server components by default. `\"use client\"` goes on the\n  small interactive leaves (a menu button, a form, a chart), never at the\n  top of a page or layout: everything a client file imports ships.\n- Props into a client component are serialisable: plain objects, arrays,\n  strings, numbers, booleans, `Date`, `Map`, `Set`, promises, server\n  actions. Not functions, class instances or database rows: pass the fields\n  shown (`backend-stack-next`). Server content goes in as `children`:\n\n```tsx\nexport default async function OrdersPage() {\n  const orders = await listOrders();  \u002F\u002F the server-only data layer\n  return (\n    \u003CFilterPanel>                    {\u002F* \"use client\": holds its open state *\u002F}\n      \u003COrderTable orders={orders} \u002F> {\u002F* still rendered on the server *\u002F}\n    \u003C\u002FFilterPanel>\n  );\n}\n```\n\n- `import \"server-only\"` in modules holding queries or secrets,\n  `import \"client-only\"` in modules touching `window`: a leak becomes a\n  build error.\n\n## 4. Data and caching\n\n- Fetch in server components, close to where the data is shown, through\n  the data layer. Never `useEffect` with `fetch(\"\u002Fapi\u002F...\")` for a page's\n  first data, and never a page calling its own route handler.\n- Start independent reads together (`Promise.all`), or give each slow part\n  its own `\u003CSuspense>` so it streams without holding the rest. `cache()`\n  from React dedupes a call within one request.\n\nWhich caching model runs is in `next.config.ts`:\n\n| | Cache Components (`cacheComponents: true`, 16) | Previous model (15, or the flag off) |\n|---|---|---|\n| Default | Nothing cached; each route prerenders a static shell and the rest streams in (Partial Prerendering) | `fetch` not cached; a route is static unless it reads request data |\n| To cache | `\"use cache\"` at the top of the function or component, with `cacheLife(\"hours\")` and `cacheTag(\"orders\")` | `fetch(url, { next: { revalidate: 3600, tags: [\"orders\"] } })`, `unstable_cache` for other reads, `export const revalidate` |\n| `cookies()`, `headers()`, `searchParams` | Read inside `\u003CSuspense>`; only that part waits | The whole route becomes dynamic |\n| `Date.now()`, random ids | After `await connection()`, inside Suspense | A dynamic route, or `export const dynamic = \"force-dynamic\"` |\n\nArguments and captured values become part of a `\"use cache\"` key: never\ncache one user's data under a shared key. Say in the report what is cached,\nfor how long, and what is dynamic.\n\n## 5. Mutations: server actions and forms\n\nServer actions (`\"use server\"`) for the app's own forms and buttons; route\nhandlers for webhooks, other clients and files (`backend-stack-next`).\nEvery action validates its input and checks the user.\n\n```ts\n\"use server\"; \u002F\u002F app\u002F(app)\u002Forders\u002Factions.ts\nimport { z } from \"zod\";\nimport { updateTag } from \"next\u002Fcache\";\nimport { redirect } from \"next\u002Fnavigation\";\nimport { insertOrder, requireUser } from \"@\u002Fserver\u002Forders\";\nconst Order = z.object({ title: z.string().trim().min(1, \"Give the order a title.\") });\nexport type OrderState = { errors?: { title?: string[] }; title?: string };\nexport async function createOrder(_: OrderState, data: FormData): Promise\u003COrderState> {\n  const user = await requireUser();\n  const parsed = Order.safeParse({ title: data.get(\"title\") });\n  if (!parsed.success) {\n    return { errors: z.flattenError(parsed.error).fieldErrors, title: String(data.get(\"title\") ?? \"\") };\n  }\n  const order = await insertOrder(user, parsed.data);\n  updateTag(\"orders\"); \u002F\u002F revalidatePath(\"\u002Forders\") before 16\n  redirect(`\u002Forders\u002F${order.id}`);\n}\n```\n\n```tsx\n\"use client\"; \u002F\u002F app\u002F(app)\u002Forders\u002Fnew\u002Forder-form.tsx\nimport { useActionState } from \"react\";\nimport { createOrder } from \"..\u002Factions\";\nexport function OrderForm() {\n  const [state, action, pending] = useActionState(createOrder, {});\n  return (\n    \u003Cform action={action}>\n      \u003Clabel htmlFor=\"title\">Title\u003C\u002Flabel>\n      \u003Cinput id=\"title\" name=\"title\" defaultValue={state.title}\n        aria-invalid={!!state.errors?.title} aria-describedby=\"title-error\" \u002F>\n      \u003Cp id=\"title-error\">{state.errors?.title?.[0]}\u003C\u002Fp>\n      \u003Cbutton type=\"submit\" disabled={pending}>{pending ? \"Creating...\" : \"Create order\"}\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  );\n}\n```\n\n- `redirect()` and `notFound()` throw: call them outside `try`\u002F`catch`.\n- After a write: `updateTag(tag)` in an action (16) shows the user their\n  change at once; elsewhere, `revalidateTag(tag, \"max\")` or `revalidatePath`.\n- Pending: `useActionState`'s third value, or `useFormStatus` in a child of\n  the form; `useOptimistic` for a list that changes before the server\n  answers. React resets an uncontrolled form after its action: on a\n  failure, return the values and feed them back as `defaultValue`.\n- A form that only navigates (search, filters) is `\u003CForm action=\"\u002Fsearch\">`\n  from `next\u002Fform`: the query lands in the URL and the route is prefetched.\n\n## 6. Loading, errors and not found\n\n- `loading.tsx` in each segment that loads data: a skeleton of the real\n  layout (the same header and columns), not a centred spinner.\n- `error.tsx` (`\"use client\"`) says what failed in words and offers a retry\n  (`retry()` from 16.3, `reset()` before); it does not cover its own\n  segment's layout. `global-error.tsx` covers the root layout with its own\n  `\u003Chtml>` and `\u003Cbody>`, without the global styles or the theme.\n- `notFound()` renders the nearest `not-found.tsx`; `app\u002Fnot-found.tsx`\n  offers a way back. Expected failures are returned and shown, not thrown\n  (`frontend-errors`); `catchError` from `next\u002Ferror` (16.3) puts a\n  boundary with a retry around one part of a page.\n\n## 7. Metadata, fonts, images, links\n\n```tsx\n\u002F\u002F app\u002Flayout.tsx; the root element: \u003Chtml lang=\"en\" className={`${display.variable} ${sans.variable}`}>\nimport type { Metadata } from \"next\";\nimport { Newsreader, Public_Sans } from \"next\u002Ffont\u002Fgoogle\"; \u002F\u002F the faces DESIGN.md names\nconst display = Newsreader({ subsets: [\"latin\"], variable: \"--font-newsreader\" });\nconst sans = Public_Sans({ subsets: [\"latin\"], variable: \"--font-public-sans\" });\nexport const metadata: Metadata = {\n  metadataBase: new URL(\"https:\u002F\u002F[domain]\"),\n  title: { default: \"[Product]\", template: \"%s · [Product]\" },\n  description: \"[What it does, in one sentence]\",\n};\n```\n\n- A title and description per page: `export const metadata`, or\n  `generateMetadata` when they depend on data (`params` is a promise; share\n  the page's read through `cache()`). `opengraph-image.tsx`, `icon.svg`,\n  `sitemap.ts` and `robots.ts` are files in `app\u002F` (`frontend-seo`).\n- Fonts only through `next\u002Ffont` (`google` or `local`), self-hosted at\n  build and exposed as CSS variables; never a `\u003Clink>` or `@import` to\n  Google Fonts. Map them under another name (`@theme inline { --font-display:\n  var(--font-newsreader); }`): a variable set to itself is invalid.\n- Images with `next\u002Fimage`: `width` and `height`, or `fill` with `sizes` in\n  a positioned parent; `sizes` that match the layout (`\"(min-width: 1024px)\n  50vw, 100vw\"`); `preload` (`priority` before 16) or `fetchPriority=\"high\"`\n  on the one image that is the largest thing on first view, and on no\n  other; remote hosts in `images.remotePatterns` (`ui-part-images`).\n- Internal links with `next\u002Flink`; they prefetch in production as they\n  scroll into view. `prefetch={false}` on long lists of row links;\n  `useLinkStatus()` inside a link for a pending hint on a slow route.\n\n## 8. Proxy, and environment variables\n\n```ts\n\u002F\u002F proxy.ts (16); before it, middleware.ts exporting `middleware`\nimport { NextResponse, type NextRequest } from \"next\u002Fserver\";\nexport function proxy(request: NextRequest) {\n  if (request.cookies.has(\"session\")) return NextResponse.next();\n  const login = new URL(\"\u002Flogin\", request.url);\n  login.searchParams.set(\"next\", request.nextUrl.pathname);\n  return NextResponse.redirect(login);\n}\nexport const config = { matcher: [\"\u002Fapp\u002F:path*\", \"\u002Fsettings\u002F:path*\"] };\n```\n\n- An optimistic redirect, not authorisation: the data layer checks the\n  user on every read and write (`backend-auth`). `middleware.ts` runs on\n  the edge runtime: no Node APIs, no database driver. The sign-in page\n  accepts only a relative `next`, or it is an open redirect.\n- `NEXT_PUBLIC_` variables are compiled into the browser bundle at build:\n  never a key, and a change needs a rebuild. Server values have no prefix\n  and are read in server-only modules; `.env.example` lists the names.\n\n## 9. Styling and traps\n\n- Styling: the project's method (Tailwind, `ui-stack-tailwind` and\n  `ui-stack-shadcn`; CSS Modules; or tokens in `app\u002Fglobals.css`), tokens\n  once. Themes: `next-themes` with `suppressHydrationWarning` (`ui-themes`).\n- Hydration mismatches: dates formatted in the server's time zone or locale\n  (pass `timeZone` and the locale, or format after mount), `Math.random()`\n  or `Date.now()` in render (`useId` for ids), `typeof window` branches, a\n  `\u003Cdiv>` in a `\u003Cp>`. `suppressHydrationWarning` only where it is meant.\n- One import pulling a whole library into a client file (charts, dates, a\n  barrel of icons): import the one function, load heavy client-only\n  widgets with `next\u002Fdynamic`, list barrels in\n  `experimental.optimizePackageImports`.\n- `searchParams` read in a layout (layouts do not get them), `params` read\n  without `await`, `cookies().set()` in a server component (only actions\n  and route handlers set cookies).\n\n## Check it\n\n- `npm run build` type-checks and prerenders every route; fix what it\n  reports. Its table marks each route static (○), prerendered with params\n  (●), partially prerendered (◐) or dynamic (ƒ): a page meant to be static\n  that shows ƒ reads request data somewhere. Next 15 prints First Load JS\n  per route; in 16, `npx next experimental-analyze` (16.1+).\n- `npm run lint` for lint (in 16 the build no longer runs it).\n- Look at the result with the `preview` tool: `url` `http:\u002F\u002Flocalhost:3000\u002F`\n  and `start` `npm run dev -- -p 3000`; it starts the server, waits for it\n  and stops it afterwards. Caching and streaming differ in dev: check them\n  against `npm run build && npm run start`, and in the page source.\n\n## Avoid\n\n`\"use client\"` at the top of pages; `useEffect` for a page's first data; a\npage fetching its own API route; a client component importing the database\nclient; fonts from a Google `\u003Clink>`; `\u003Cimg>` for content images; `preload`\non several images; auth only in proxy or middleware; a key in a\n`NEXT_PUBLIC_` variable; one user's data in a shared cache; `redirect()`\ninside `try`; a spinner where `loading.tsx` could show the layout; the\nstarter's SVGs, font and favicon left in place.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fui-stack-next",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":54,"prompt":64,"zipAvailable":65},[55,58,61],{"tool":56,"command":57},"npx","npx skills add enowdev\u002Fenowxcli --skill ui-stack-next",{"tool":59,"command":60},"bunx","bunx skills add enowdev\u002Fenowxcli --skill ui-stack-next",{"tool":62,"command":63},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill ui-stack-next","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-next\u002FSKILL.md",true,[],[68,83,108,121,148,170],{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":72,"categories":73,"keywords":79,"publishedAt":39},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[74],{"categoryNo":75,"categoryName":76,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[80,81,82,25,32],"demo skill","demo","tests",{"skillNo":84,"name":85,"nameZh":6,"description":86,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":97,"publishedAt":39},"01M6D2HM0GCPM0TM2HPG9HHR2R","writing","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[89,92],{"categoryNo":90,"categoryName":91,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[85,98,99,100,101,102,32,103,104,105,106,107],"text","emails","empty","drop","document","page","voice","notifications","documentation","translate",{"skillNo":109,"name":20,"nameZh":6,"description":110,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":116,"publishedAt":39},"01M6D2HG07YS49NM196Q0R3AZ8","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[113],{"categoryNo":114,"categoryName":115,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000032","设计师",[20,117,38,118,119,103,24,32,120],"icons","SET","responsive","before building",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":133,"publishedAt":39},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[127,130],{"categoryNo":128,"categoryName":129,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":131,"categoryName":132,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000030","开发工程师",[20,134,135,136,137,138,139,140,141,32,120,142,103,143,144,145,117,146,147],"ui themes","switching","photos","tokens","drawing","focus","maps","Art","flash","code","Charts","content","screenshots","logo",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":157,"publishedAt":39},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[154,155,156],{"categoryNo":90,"categoryName":91,"taxonomyType":77,"taxonomyTypeDesc":78},{"categoryNo":131,"categoryName":132,"taxonomyType":95,"taxonomyTypeDesc":96},{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},[158,20,159,38,160,161,143,162,163,164,24,137,165,166,167,32,120,103,168,169],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","primitives","list","why","how",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":183,"publishedAt":39},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue","Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[176,179,182],{"categoryNo":177,"categoryName":178,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":180,"categoryName":181,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":114,"categoryName":115,"taxonomyType":95,"taxonomyTypeDesc":96},[20,184,21,185,186,187,188,189,24,25,190,23,32,191,192,193,36,194,195,196],"vue","ui stack vue","forms","pages","watch","runtime","transitions","pinia","nuxt","template","config","directory","testing"]