<!-- skill: Vue Nuxt | version: 1.0.0 | description: Nuxt 3 Composition API, SSR data fetching and Nitro deploys | category: Web -->
## Instructions
- Composition API first: `<script setup lang="ts">` with `ref/reactive/computed/watch`; no Options API in new code; extract shared logic to `composables/useX.ts` auto-imported.
- File routing: `pages/index.vue` + `pages/blog/[slug].vue`, `layouts/default.vue` via `<NuxtLayout>`, `middleware/auth.ts` with `defineNuxtRouteMiddleware`, `error.vue` root boundary.
- Data fetching: `useAsyncData(key, fn)` for custom logic + `useFetch(url)` for simple GET; pass `lazy:true` for non-blocking, `server:false` for client-only; never `fetch()` in `onMounted` for SEO content.
- SSR vs SPA: default `ssr:true`; use `<ClientOnly>` for browser-only widgets; `useState()` for SSR-safe shared state instead of module singletons that leak between requests.
- Nitro + server: `server/api/*.ts` with `defineEventHandler`, `getQuery/readBody`, `createError({statusCode})`; `server/utils/` for DB clients; `runtimeConfig` (private) vs `runtimeConfig.public`.
- SEO/meta: `useSeoMeta({title, description, ogImage})` + `useHead({script, link})` per page; `nuxt.config.ts` sets `siteUrl`, `robots`, canonical; prerender marketing routes with `nitro.prerender.routes`.
- State/Pinia: `@pinia/nuxt` for cross-page state, persist auth token in `useCookie('token', {httpOnly:false, secure:true, sameSite:'lax'})`; hydrate store from `useAsyncData` on layout.
- Styling: Tailwind `@nuxtjs/tailwindcss` or Nuxt UI; `app.vue` holds `<NuxtPage/>`; scoped `<style scoped>` for page quirks, design tokens in `assets/css/main.css`.
- Typing: `types/index.ts` shared, `useFetch<Type>` generics, `server/api` returns typed via `zod` validation; `nuxi typecheck` clean before deploy.
- Images/static: `~/assets/` bundled + processed, `/public/` served verbatim; use `<NuxtImg>` from `@nuxt/image` with `sizes="sm:100vw md:50vw"` + AVIF.
- Auth: cookie session via `h3` `setCookie/getCookie`, `middleware` checks `useSupabaseUser()` or custom `/api/me`; refresh on 401 inside `$fetch` interceptor plugin.
- Deploy: Nitro presets `node-server` (VPS), `vercel`, `cloudflare`; `NITRO_PRESET` env, `nuxt build` outputs `.output/`; set `NUXT_*` env vars on host.
## Examples
- Marketing + blog: prerendered `/` + `/pricing`, SSR `/blog/[slug]` via `useAsyncData` keyed by slug with `useSeoMeta` titles, ISR via `routeRules: {'/blog/**': {isr: 300}}`.
- SaaS dashboard: `layouts/app.vue` sidebar, `middleware/auth.ts` redirect, `composables/useProjects.ts` wrapping `useFetch('/api/projects')` with optimistic add.
- API proxy: `server/api/github.ts` forwards with private token from `runtimeConfig`, caches 60s with `cachedEventHandler`, client calls `/api/github` so secret never leaks.
## Workflows
1. Scaffold: `npx nuxi@latest init app` then `@pinia/nuxt @nuxtjs/tailwindcss @nuxt/image` modules, configure `runtimeConfig` + `routeRules`.
2. Build pages/layouts: `app.vue`, `pages/`, `layouts/`, `error.vue`, shared `components/` with auto-imports.
3. Add data: `useFetch/useAsyncData` per page, `server/api/` handlers with Zod, `useSeoMeta` per route.
4. Add auth/state: Pinia stores + cookie session, route middleware, `$fetch` plugin for 401 refresh.
5. Harden: `nuxi typecheck`, `nuxt build` + `nuxt preview`, Lighthouse >90, check SSR HTML via view-source.
6. Deploy: pick Nitro preset, set env (`NUXT_PUBLIC_*`), `node .output/server/index.mjs` or push to Vercel/Cloudflare.
## Templates
- SSR page with useAsyncData + SEO:
```vue
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: post, error } = await useAsyncData(`post-${route.params.slug}`, () =>
  $fetch(`/api/posts/${route.params.slug}`));
if (error.value) throw createError({ statusCode: 404, statusMessage: 'Post not found' });
useSeoMeta({ title: () => post.value?.title ?? 'Blog', description: () => post.value?.excerpt ?? '' });
</script>
<template>
  <article v-if="post" class="prose mx-auto"><h1>{{ post.title }}</h1><div v-html="post.html" /></article>
</template>
```
- Auth middleware + API handler:
```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware(() => {
  const user = useState<any>('user', () => null);
  if (!user.value) return navigateTo('/login');
});
// server/api/me.ts
export default defineEventHandler(async (event) => {
  const token = getCookie(event, 'session');
  if (!token) throw createError({ statusCode: 401, statusMessage: 'Unauthenticated' });
  return { id: 'u_1', email: 'dev@example.com' };
});
```
- Composable with useFetch:
```ts
// composables/useProjects.ts
export function useProjects() {
  const { data, pending, refresh } = useFetch('/api/projects', { lazy: true });
  async function add(name: string) {
    await $fetch('/api/projects', { method: 'POST', body: { name } });
    await refresh();
  }
  return { projects: data, pending, add, refresh };
}
```
## Tools
- SARAH shell_run for `nuxi dev/build/preview/typecheck`, `npm run lint`, Lighthouse, `node .output/server/index.mjs` smoke test.
- SARAH file_write for pages/layouts/composables/server-api; web_fetch for Nuxt docs + Nitro presets.
## Advanced
- routeRules power: `{ '/old/**': { redirect: '/new' }, '/api/**': { cors: true }, '/blog/**': { isr: 300 }, '/app/**': { ssr: false } }` hybrid SSR/SPA/ISR in one app.
- Caching: `cachedEventHandler(fn, { maxAge: 60, swr: true })` on server, `getCachedData` key fn on `useFetch` to reuse payloads across navigations.
- Deploy matrix: VPS `NITRO_PRESET=node-server` + PM2 + Nginx TLS; Vercel zero-config; Cloudflare `NITRO_PRESET=cloudflare` with KV session; set `compatibilityVersion: 4` in config.
## Documentation
- https://nuxt.com/docs/getting-started/introduction
- https://nuxt.com/docs/api/composables/use-fetch
- https://nuxt.com/docs/api/composables/use-async-data
- https://nitro.unjs.io/deploy
- https://nuxt.com/docs/guide/concepts/server-engine
