format: apply repo prettier (3.8.1) to the folded skills tree

963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
This commit is contained in:
fargo
2026-08-19 14:37:17 -05:00
parent d2eeb64433
commit 1a822493ba
962 changed files with 29594 additions and 27188 deletions
+27 -27
View File
@@ -3,7 +3,7 @@ name: nuxt
description: Nuxt full-stack Vue framework with SSR, auto-imports, and file-based routing. Use when working with Nuxt apps, server routes, useFetch, middleware, or hybrid rendering.
metadata:
author: Anthony Fu
version: "2026.1.28"
version: '2026.1.28'
source: Generated from https://github.com/nuxt/nuxt, scripts located at https://github.com/antfu/skills
---
@@ -13,43 +13,43 @@ Nuxt is a full-stack Vue framework that provides server-side rendering, file-bas
## Core
| Topic | Description | Reference |
|-------|-------------|-----------|
| Directory Structure | Project folder structure, conventions, file organization | [core-directory-structure](references/core-directory-structure.md) |
| Configuration | nuxt.config.ts, app.config.ts, runtime config, environment variables | [core-config](references/core-config.md) |
| CLI Commands | Dev server, build, generate, preview, and utility commands | [core-cli](references/core-cli.md) |
| Routing | File-based routing, dynamic routes, navigation, middleware, layouts | [core-routing](references/core-routing.md) |
| Data Fetching | useFetch, useAsyncData, $fetch, caching, refresh | [core-data-fetching](references/core-data-fetching.md) |
| Modules | Creating and using Nuxt modules, Nuxt Kit utilities | [core-modules](references/core-modules.md) |
| Deployment | Platform-agnostic deployment with Nitro, Vercel, Netlify, Cloudflare | [core-deployment](references/core-deployment.md) |
| Topic | Description | Reference |
| ------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Directory Structure | Project folder structure, conventions, file organization | [core-directory-structure](references/core-directory-structure.md) |
| Configuration | nuxt.config.ts, app.config.ts, runtime config, environment variables | [core-config](references/core-config.md) |
| CLI Commands | Dev server, build, generate, preview, and utility commands | [core-cli](references/core-cli.md) |
| Routing | File-based routing, dynamic routes, navigation, middleware, layouts | [core-routing](references/core-routing.md) |
| Data Fetching | useFetch, useAsyncData, $fetch, caching, refresh | [core-data-fetching](references/core-data-fetching.md) |
| Modules | Creating and using Nuxt modules, Nuxt Kit utilities | [core-modules](references/core-modules.md) |
| Deployment | Platform-agnostic deployment with Nitro, Vercel, Netlify, Cloudflare | [core-deployment](references/core-deployment.md) |
## Features
| Topic | Description | Reference |
|-------|-------------|-----------|
| Composables Auto-imports | Vue APIs, Nuxt composables, custom composables, utilities | [features-composables](references/features-composables.md) |
| Components Auto-imports | Component naming, lazy loading, hydration strategies | [features-components-autoimport](references/features-components-autoimport.md) |
| Built-in Components | NuxtLink, NuxtPage, NuxtLayout, ClientOnly, and more | [features-components](references/features-components.md) |
| State Management | useState composable, SSR-friendly state, Pinia integration | [features-state](references/features-state.md) |
| Server Routes | API routes, server middleware, Nitro server engine | [features-server](references/features-server.md) |
| Topic | Description | Reference |
| ------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Composables Auto-imports | Vue APIs, Nuxt composables, custom composables, utilities | [features-composables](references/features-composables.md) |
| Components Auto-imports | Component naming, lazy loading, hydration strategies | [features-components-autoimport](references/features-components-autoimport.md) |
| Built-in Components | NuxtLink, NuxtPage, NuxtLayout, ClientOnly, and more | [features-components](references/features-components.md) |
| State Management | useState composable, SSR-friendly state, Pinia integration | [features-state](references/features-state.md) |
| Server Routes | API routes, server middleware, Nitro server engine | [features-server](references/features-server.md) |
## Rendering
| Topic | Description | Reference |
|-------|-------------|-----------|
| Topic | Description | Reference |
| --------------- | ----------------------------------------------------------------- | ------------------------------------------------ |
| Rendering Modes | Universal (SSR), client-side (SPA), hybrid rendering, route rules | [rendering-modes](references/rendering-modes.md) |
## Best Practices
| Topic | Description | Reference |
|-------|-------------|-----------|
| Data Fetching Patterns | Efficient fetching, caching, parallel requests, error handling | [best-practices-data-fetching](references/best-practices-data-fetching.md) |
| SSR & Hydration | Avoiding context leaks, hydration mismatches, composable patterns | [best-practices-ssr](references/best-practices-ssr.md) |
| Topic | Description | Reference |
| ---------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Data Fetching Patterns | Efficient fetching, caching, parallel requests, error handling | [best-practices-data-fetching](references/best-practices-data-fetching.md) |
| SSR & Hydration | Avoiding context leaks, hydration mismatches, composable patterns | [best-practices-ssr](references/best-practices-ssr.md) |
## Advanced
| Topic | Description | Reference |
|-------|-------------|-----------|
| Layers | Extending applications with reusable layers | [advanced-layers](references/advanced-layers.md) |
| Lifecycle Hooks | Build-time, runtime, and server hooks | [advanced-hooks](references/advanced-hooks.md) |
| Topic | Description | Reference |
| ---------------- | ----------------------------------------------- | -------------------------------------------------------------------- |
| Layers | Extending applications with reusable layers | [advanced-layers](references/advanced-layers.md) |
| Lifecycle Hooks | Build-time, runtime, and server hooks | [advanced-hooks](references/advanced-hooks.md) |
| Module Authoring | Creating publishable Nuxt modules with Nuxt Kit | [advanced-module-authoring](references/advanced-module-authoring.md) |
@@ -18,7 +18,7 @@ Used in `nuxt.config.ts` or modules:
export default defineNuxtConfig({
hooks: {
'build:before': () => {
console.log('Build starting...')
console.log('Build starting...');
},
'pages:extend': (pages) => {
// Add custom pages
@@ -26,14 +26,14 @@ export default defineNuxtConfig({
name: 'custom',
path: '/custom',
file: '~/pages/custom.vue',
})
});
},
'components:dirs': (dirs) => {
// Add component directories
dirs.push({ path: '~/extra-components' })
dirs.push({ path: '~/extra-components' });
},
},
})
});
```
### In Modules
@@ -43,35 +43,35 @@ export default defineNuxtConfig({
export default defineNuxtModule({
setup(options, nuxt) {
nuxt.hook('ready', async (nuxt) => {
console.log('Nuxt is ready')
})
console.log('Nuxt is ready');
});
nuxt.hook('close', async (nuxt) => {
console.log('Nuxt is closing')
})
console.log('Nuxt is closing');
});
nuxt.hook('modules:done', () => {
console.log('All modules loaded')
})
console.log('All modules loaded');
});
},
})
});
```
### Common Build Hooks
| Hook | When |
|------|------|
| `ready` | Nuxt initialization complete |
| `close` | Nuxt is closing |
| `modules:done` | All modules installed |
| `build:before` | Before build starts |
| `build:done` | Build complete |
| `pages:extend` | Pages routes resolved |
| `components:dirs` | Component dirs being resolved |
| `imports:extend` | Auto-imports being resolved |
| `nitro:config` | Before Nitro config finalized |
| `vite:extend` | Vite context created |
| `vite:extendConfig` | Before Vite config finalized |
| Hook | When |
| ------------------- | ----------------------------- |
| `ready` | Nuxt initialization complete |
| `close` | Nuxt is closing |
| `modules:done` | All modules installed |
| `build:before` | Before build starts |
| `build:done` | Build complete |
| `pages:extend` | Pages routes resolved |
| `components:dirs` | Component dirs being resolved |
| `imports:extend` | Auto-imports being resolved |
| `nitro:config` | Before Nitro config finalized |
| `vite:extend` | Vite context created |
| `vite:extendConfig` | Before Vite config finalized |
## App Hooks (Runtime)
@@ -83,54 +83,54 @@ Used in plugins and composables:
// plugins/lifecycle.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', (vueApp) => {
console.log('Vue app created')
})
console.log('Vue app created');
});
nuxtApp.hook('app:mounted', (vueApp) => {
console.log('App mounted')
})
console.log('App mounted');
});
nuxtApp.hook('page:start', () => {
console.log('Page navigation starting')
})
console.log('Page navigation starting');
});
nuxtApp.hook('page:finish', () => {
console.log('Page navigation finished')
})
console.log('Page navigation finished');
});
nuxtApp.hook('page:loading:start', () => {
console.log('Page loading started')
})
console.log('Page loading started');
});
nuxtApp.hook('page:loading:end', () => {
console.log('Page loading ended')
})
})
console.log('Page loading ended');
});
});
```
### Common App Hooks
| Hook | When |
|------|------|
| `app:created` | Vue app created |
| `app:mounted` | Vue app mounted (client only) |
| `app:error` | Fatal error occurred |
| `page:start` | Page navigation starting |
| `page:finish` | Page navigation finished |
| Hook | When |
| -------------------- | ----------------------------- |
| `app:created` | Vue app created |
| `app:mounted` | Vue app mounted (client only) |
| `app:error` | Fatal error occurred |
| `page:start` | Page navigation starting |
| `page:finish` | Page navigation finished |
| `page:loading:start` | Loading indicator should show |
| `page:loading:end` | Loading indicator should hide |
| `link:prefetch` | Link is being prefetched |
| `page:loading:end` | Loading indicator should hide |
| `link:prefetch` | Link is being prefetched |
### Using Runtime Hooks
```ts
// composables/usePageTracking.ts
export function usePageTracking() {
const nuxtApp = useNuxtApp()
const nuxtApp = useNuxtApp();
nuxtApp.hook('page:finish', () => {
trackPageView(useRoute().path)
})
trackPageView(useRoute().path);
});
}
```
@@ -143,37 +143,37 @@ Used in server plugins:
export default defineNitroPlugin((nitroApp) => {
// Modify HTML before sending
nitroApp.hooks.hook('render:html', (html, { event }) => {
html.head.push('<meta name="custom" content="value">')
html.bodyAppend.push('<script>console.log("injected")</script>')
})
html.head.push('<meta name="custom" content="value">');
html.bodyAppend.push('<script>console.log("injected")</script>');
});
// Modify response
nitroApp.hooks.hook('render:response', (response, { event }) => {
console.log('Sending response:', response.statusCode)
})
console.log('Sending response:', response.statusCode);
});
// Before request
nitroApp.hooks.hook('request', (event) => {
console.log('Request:', event.path)
})
console.log('Request:', event.path);
});
// After response
nitroApp.hooks.hook('afterResponse', (event) => {
console.log('Response sent')
})
})
console.log('Response sent');
});
});
```
### Common Nitro Hooks
| Hook | When |
|------|------|
| `request` | Request received |
| `beforeResponse` | Before sending response |
| `afterResponse` | After response sent |
| `render:html` | Before HTML is sent |
| Hook | When |
| ----------------- | ---------------------------- |
| `request` | Request received |
| `beforeResponse` | Before sending response |
| `afterResponse` | After response sent |
| `render:html` | Before HTML is sent |
| `render:response` | Before response is finalized |
| `error` | Error occurred |
| `error` | Error occurred |
## Custom Hooks
@@ -181,23 +181,23 @@ export default defineNitroPlugin((nitroApp) => {
```ts
// types/hooks.d.ts
import type { HookResult } from '@nuxt/schema'
import type { HookResult } from '@nuxt/schema';
declare module '#app' {
interface RuntimeNuxtHooks {
'my-app:event': (data: MyEventData) => HookResult
'my-app:event': (data: MyEventData) => HookResult;
}
}
declare module '@nuxt/schema' {
interface NuxtHooks {
'my-module:init': () => HookResult
'my-module:init': () => HookResult;
}
}
declare module 'nitropack/types' {
interface NitroRuntimeHooks {
'my-server:event': (data: any) => void
'my-server:event': (data: any) => void;
}
}
```
@@ -208,15 +208,15 @@ declare module 'nitropack/types' {
// In a plugin
export default defineNuxtPlugin((nuxtApp) => {
// Call custom hook
nuxtApp.callHook('my-app:event', { type: 'custom' })
})
nuxtApp.callHook('my-app:event', { type: 'custom' });
});
// In a module
export default defineNuxtModule({
setup(options, nuxt) {
nuxt.callHook('my-module:init')
nuxt.callHook('my-module:init');
},
})
});
```
## useRuntimeHook
@@ -227,8 +227,8 @@ Call hooks at runtime from components:
<script setup lang="ts">
// Register a callback for a runtime hook
useRuntimeHook('app:error', (error) => {
console.error('App error:', error)
})
console.error('App error:', error);
});
</script>
```
@@ -240,13 +240,13 @@ useRuntimeHook('app:error', (error) => {
// plugins/analytics.client.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:finish', () => {
const route = useRoute()
const route = useRoute();
analytics.track('pageview', {
path: route.path,
title: document.title,
})
})
})
});
});
});
```
### Performance Monitoring
@@ -254,17 +254,17 @@ export default defineNuxtPlugin((nuxtApp) => {
```ts
// plugins/performance.client.ts
export default defineNuxtPlugin((nuxtApp) => {
let navigationStart: number
let navigationStart: number;
nuxtApp.hook('page:start', () => {
navigationStart = performance.now()
})
navigationStart = performance.now();
});
nuxtApp.hook('page:finish', () => {
const duration = performance.now() - navigationStart
console.log(`Navigation took ${duration}ms`)
})
})
const duration = performance.now() - navigationStart;
console.log(`Navigation took ${duration}ms`);
});
});
```
### Inject HTML
@@ -277,12 +277,12 @@ export default defineNitroPlugin((nitroApp) => {
<script>
window.APP_CONFIG = ${JSON.stringify(config)}
</script>
`)
})
})
`);
});
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/guide/going-further/hooks
- https://nuxt.com/docs/api/advanced/hooks
@@ -14,11 +14,8 @@ Layers allow sharing and reusing partial Nuxt applications across projects. They
```ts
// nuxt.config.ts
export default defineNuxtConfig({
extends: [
'@my-org/base-layer',
'@nuxtjs/ui-layer',
],
})
extends: ['@my-org/base-layer', '@nuxtjs/ui-layer'],
});
```
### From Git Repository
@@ -34,7 +31,7 @@ export default defineNuxtConfig({
'gitlab:username/repo',
'bitbucket:username/repo',
],
})
});
```
### From Local Directory
@@ -42,11 +39,8 @@ export default defineNuxtConfig({
```ts
// nuxt.config.ts
export default defineNuxtConfig({
extends: [
'../base-layer',
'./layers/shared',
],
})
extends: ['../base-layer', './layers/shared'],
});
```
### Auto-scanned Layers
@@ -95,7 +89,7 @@ export default defineNuxtConfig({
},
// Shared modules
modules: ['@nuxt/ui'],
})
});
```
### Layer Components
@@ -122,15 +116,16 @@ Use in consuming project:
```ts
// my-layer/app/composables/useTheme.ts
export function useTheme() {
const isDark = useState('theme-dark', () => false)
const toggle = () => isDark.value = !isDark.value
return { isDark, toggle }
const isDark = useState('theme-dark', () => false);
const toggle = () => (isDark.value = !isDark.value);
return { isDark, toggle };
}
```
## Layer Priority
Override order (highest to lowest):
1. Your project files
2. Auto-scanned layers (alphabetically, Z > A)
3. `extends` array (first > last)
@@ -149,7 +144,7 @@ Access layer files:
```ts
// Auto-scanned layers get aliases
import Component from '#layers/base/components/Component.vue'
import Component from '#layers/base/components/Component.vue';
```
Named aliases:
@@ -160,12 +155,12 @@ export default defineNuxtConfig({
$meta: {
name: 'my-layer',
},
})
});
```
```ts
// In consuming project
import { something } from '#layers/my-layer/utils'
import { something } from '#layers/my-layer/utils';
```
## Publishing Layers
@@ -201,16 +196,14 @@ export GIGET_AUTH=<github-token>
```ts
// my-layer/nuxt.config.ts
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const currentDir = dirname(fileURLToPath(import.meta.url))
const currentDir = dirname(fileURLToPath(import.meta.url));
export default defineNuxtConfig({
css: [
join(currentDir, './assets/main.css'),
],
})
css: [join(currentDir, './assets/main.css')],
});
```
### Install Dependencies
@@ -218,10 +211,8 @@ export default defineNuxtConfig({
```ts
// nuxt.config.ts
export default defineNuxtConfig({
extends: [
['github:user/layer', { install: true }],
],
})
extends: [['github:user/layer', { install: true }]],
});
```
### Disable Layer Modules
@@ -233,7 +224,7 @@ export default defineNuxtConfig({
// Disable modules from layer
image: false, // Disables @nuxt/image
pinia: false, // Disables @pinia/nuxt
})
});
```
## Starter Template
@@ -265,7 +256,7 @@ theme-layer/
// theme-layer/nuxt.config.ts
export default defineNuxtConfig({
css: ['~/assets/theme.css'],
})
});
```
```ts
@@ -275,24 +266,24 @@ export default defineAppConfig({
primaryColor: '#00dc82',
darkMode: false,
},
})
});
```
```ts
// consuming-app/nuxt.config.ts
export default defineNuxtConfig({
extends: ['theme-layer'],
})
});
// consuming-app/app/app.config.ts
export default defineAppConfig({
theme: {
primaryColor: '#ff0000', // Override
},
})
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/layers
- https://nuxt.com/docs/guide/going-further/layers
@@ -31,12 +31,12 @@ my-nuxt-module/
```ts
// src/module.ts
import { defineNuxtModule, createResolver, addPlugin, addComponent, addImports } from '@nuxt/kit'
import { defineNuxtModule, createResolver, addPlugin, addComponent, addImports } from '@nuxt/kit';
export interface ModuleOptions {
prefix?: string
apiKey: string
enabled?: boolean
prefix?: string;
apiKey: string;
enabled?: boolean;
}
export default defineNuxtModule<ModuleOptions>({
@@ -52,13 +52,13 @@ export default defineNuxtModule<ModuleOptions>({
enabled: true,
},
setup(options, nuxt) {
if (!options.enabled) return
if (!options.enabled) return;
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Module setup logic here
},
})
});
```
### Using `.with()` for Strict Type Inference
@@ -66,12 +66,12 @@ export default defineNuxtModule<ModuleOptions>({
When you need TypeScript to infer that default values are always present:
```ts
import { defineNuxtModule } from '@nuxt/kit'
import { defineNuxtModule } from '@nuxt/kit';
interface ModuleOptions {
apiKey: string
baseURL: string
timeout?: number
apiKey: string;
baseURL: string;
timeout?: number;
}
export default defineNuxtModule<ModuleOptions>().with({
@@ -87,7 +87,7 @@ export default defineNuxtModule<ModuleOptions>().with({
// resolvedOptions.baseURL is guaranteed to be string (not undefined)
// resolvedOptions.timeout is guaranteed to be number (not undefined)
},
})
});
```
## Adding Runtime Assets
@@ -95,63 +95,63 @@ export default defineNuxtModule<ModuleOptions>().with({
### Components
```ts
import { addComponent, addComponentsDir, createResolver } from '@nuxt/kit'
import { addComponent, addComponentsDir, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Single component
addComponent({
name: 'MyButton',
filePath: resolve('./runtime/components/MyButton.vue'),
})
});
// Component directory with prefix
addComponentsDir({
path: resolve('./runtime/components'),
prefix: 'My',
pathPrefix: false,
})
});
},
})
});
```
### Composables and Auto-imports
```ts
import { addImports, addImportsDir, createResolver } from '@nuxt/kit'
import { addImports, addImportsDir, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Single import
addImports({
name: 'useMyUtil',
from: resolve('./runtime/composables/useMyUtil'),
})
});
// Directory of composables
addImportsDir(resolve('./runtime/composables'))
addImportsDir(resolve('./runtime/composables'));
},
})
});
```
### Plugins
```ts
import { addPlugin, addPluginTemplate, createResolver } from '@nuxt/kit'
import { addPlugin, addPluginTemplate, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup(options) {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Static plugin file
addPlugin({
src: resolve('./runtime/plugins/myPlugin'),
mode: 'client', // 'client', 'server', or 'all'
})
});
// Dynamic plugin with generated code
addPluginTemplate({
@@ -166,9 +166,9 @@ export default defineNuxtPlugin({
// Plugin logic
}
})`,
})
});
},
})
});
```
## Server Extensions
@@ -176,67 +176,67 @@ export default defineNuxtPlugin({
### Server Handlers
```ts
import { addServerHandler, addServerScanDir, createResolver } from '@nuxt/kit'
import { addServerHandler, addServerScanDir, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Single handler
addServerHandler({
route: '/api/my-endpoint',
handler: resolve('./runtime/server/api/my-endpoint'),
})
});
// Scan entire server directory (api/, routes/, middleware/, utils/)
addServerScanDir(resolve('./runtime/server'))
addServerScanDir(resolve('./runtime/server'));
},
})
});
```
### Server Composables
```ts
import { addServerImports, addServerImportsDir, createResolver } from '@nuxt/kit'
import { addServerImports, addServerImportsDir, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Single server import
addServerImports({
name: 'useServerUtil',
from: resolve('./runtime/server/utils/useServerUtil'),
})
});
// Server composables directory
addServerImportsDir(resolve('./runtime/server/composables'))
addServerImportsDir(resolve('./runtime/server/composables'));
},
})
});
```
### Nitro Plugin
```ts
import { addServerPlugin, createResolver } from '@nuxt/kit'
import { addServerPlugin, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
addServerPlugin(resolve('./runtime/server/plugin'))
const { resolve } = createResolver(import.meta.url);
addServerPlugin(resolve('./runtime/server/plugin'));
},
})
});
```
```ts
// runtime/server/plugin.ts
import { defineNitroPlugin } from 'nitropack/runtime'
import { defineNitroPlugin } from 'nitropack/runtime';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('request', (event) => {
console.log('Request:', event.path)
})
})
console.log('Request:', event.path);
});
});
```
## Templates and Virtual Files
@@ -244,17 +244,17 @@ export default defineNitroPlugin((nitroApp) => {
### Generate Virtual Files
```ts
import { addTemplate, addTypeTemplate, addServerTemplate, createResolver } from '@nuxt/kit'
import { addTemplate, addTypeTemplate, addServerTemplate, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Client/build virtual file (accessible via #build/my-config.mjs)
addTemplate({
filename: 'my-config.mjs',
getContents: () => `export default ${JSON.stringify(options)}`,
})
});
// Type declarations
addTypeTemplate({
@@ -265,15 +265,15 @@ declare module '#my-module' {
apiKey: string
}
}`,
})
});
// Nitro virtual file (accessible in server routes)
addServerTemplate({
filename: '#my-module/config.mjs',
getContents: () => `export const config = ${JSON.stringify(options)}`,
})
});
},
})
});
```
### Access Virtual Files
@@ -281,20 +281,20 @@ declare module '#my-module' {
```ts
// In runtime plugin
// @ts-expect-error - virtual file
import config from '#build/my-config.mjs'
import config from '#build/my-config.mjs';
// In server routes
import { config } from '#my-module/config.js'
import { config } from '#my-module/config.js';
```
## Extending Pages and Routes
```ts
import { extendPages, extendRouteRules, addRouteMiddleware, createResolver } from '@nuxt/kit'
import { extendPages, extendRouteRules, addRouteMiddleware, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Add pages
extendPages((pages) => {
@@ -302,22 +302,22 @@ export default defineNuxtModule({
name: 'my-page',
path: '/my-route',
file: resolve('./runtime/pages/MyPage.vue'),
})
})
});
});
// Add route rules (caching, redirects, etc.)
extendRouteRules('/api/**', {
cache: { maxAge: 60 },
})
});
// Add middleware
addRouteMiddleware({
name: 'my-middleware',
path: resolve('./runtime/middleware/myMiddleware'),
global: true,
})
});
},
})
});
```
## Module Dependencies
@@ -351,7 +351,7 @@ export default defineNuxtModule({
setup() {
// Dependencies are guaranteed to be set up before this runs
},
})
});
```
### Dynamic Dependencies
@@ -382,16 +382,16 @@ export default defineNuxtModule({
},
onInstall(nuxt) {
// First-time setup
console.log('Module installed for the first time')
console.log('Module installed for the first time');
},
onUpgrade(nuxt, options, previousVersion) {
// Version upgrade migrations
console.log(`Upgrading from ${previousVersion}`)
console.log(`Upgrading from ${previousVersion}`);
},
setup(options, nuxt) {
// Regular setup runs every build
},
})
});
```
## Extending Configuration
@@ -400,22 +400,22 @@ export default defineNuxtModule({
export default defineNuxtModule({
setup(options, nuxt) {
// Add CSS
nuxt.options.css.push('my-module/styles.css')
nuxt.options.css.push('my-module/styles.css');
// Add runtime config
nuxt.options.runtimeConfig.public.myModule = {
apiUrl: options.apiUrl,
}
};
// Extend Vite config
nuxt.options.vite.optimizeDeps ||= {}
nuxt.options.vite.optimizeDeps.include ||= []
nuxt.options.vite.optimizeDeps.include.push('some-package')
nuxt.options.vite.optimizeDeps ||= {};
nuxt.options.vite.optimizeDeps.include ||= [];
nuxt.options.vite.optimizeDeps.include.push('some-package');
// Add build transpile
nuxt.options.build.transpile.push('my-package')
nuxt.options.build.transpile.push('my-package');
},
})
});
```
## Using Hooks
@@ -425,7 +425,7 @@ export default defineNuxtModule({
// Declarative hooks
hooks: {
'components:dirs': (dirs) => {
dirs.push({ path: '~/extra' })
dirs.push({ path: '~/extra' });
},
},
@@ -433,45 +433,42 @@ export default defineNuxtModule({
// Programmatic hooks
nuxt.hook('pages:extend', (pages) => {
// Modify pages
})
});
nuxt.hook('imports:extend', (imports) => {
imports.push({ name: 'myHelper', from: 'my-package' })
})
imports.push({ name: 'myHelper', from: 'my-package' });
});
nuxt.hook('nitro:config', (config) => {
// Modify Nitro config
})
});
nuxt.hook('vite:extendConfig', (config) => {
// Modify Vite config
})
});
},
})
});
```
## Path Resolution
```ts
import { createResolver, resolvePath, findPath } from '@nuxt/kit'
import { createResolver, resolvePath, findPath } from '@nuxt/kit';
export default defineNuxtModule({
async setup(options, nuxt) {
// Resolver relative to module
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
const pluginPath = resolve('./runtime/plugin')
const pluginPath = resolve('./runtime/plugin');
// Resolve with extensions and aliases
const entrypoint = await resolvePath('@some/package')
const entrypoint = await resolvePath('@some/package');
// Find first existing file
const configPath = await findPath([
resolve('./config.ts'),
resolve('./config.js'),
])
const configPath = await findPath([resolve('./config.ts'), resolve('./config.js')]);
},
})
});
```
## Module Package.json
@@ -520,7 +517,7 @@ export default defineNuxtConfig({
myModule: {
enabled: false,
},
})
});
```
## Development Workflow
@@ -9,12 +9,12 @@ Effective data fetching patterns for SSR-friendly, performant Nuxt applications.
## Choose the Right Tool
| Scenario | Use |
|----------|-----|
| Component initial data | `useFetch` or `useAsyncData` |
| User interactions (clicks, forms) | `$fetch` |
| Third-party SDK/API | `useAsyncData` with custom function |
| Multiple parallel requests | `useAsyncData` with `Promise.all` |
| Scenario | Use |
| --------------------------------- | ----------------------------------- |
| Component initial data | `useFetch` or `useAsyncData` |
| User interactions (clicks, forms) | `$fetch` |
| Third-party SDK/API | `useAsyncData` with custom function |
| Multiple parallel requests | `useAsyncData` with `Promise.all` |
## Await vs Non-Await Usage
@@ -25,7 +25,7 @@ The `await` keyword controls whether data fetching **blocks navigation**:
```vue
<script setup lang="ts">
// Navigation waits until data is fetched (uses Vue Suspense)
const { data } = await useFetch('/api/posts')
const { data } = await useFetch('/api/posts');
// data.value is available immediately after this line
</script>
```
@@ -39,7 +39,7 @@ const { data } = await useFetch('/api/posts')
```vue
<script setup lang="ts">
// Navigation proceeds immediately, data fetches in background
const { data, status } = useFetch('/api/posts', { lazy: true })
const { data, status } = useFetch('/api/posts', { lazy: true });
// data.value may be undefined initially - check status!
</script>
@@ -53,17 +53,17 @@ Equivalent to using `useLazyFetch`:
```vue
<script setup lang="ts">
const { data, status } = useLazyFetch('/api/posts')
const { data, status } = useLazyFetch('/api/posts');
</script>
```
### When to Use Each
| Pattern | Use Case |
|---------|----------|
| `await useFetch()` | Critical data needed for SEO/initial render |
| Pattern | Use Case |
| -------------------------- | ----------------------------------------------- |
| `await useFetch()` | Critical data needed for SEO/initial render |
| `useFetch({ lazy: true })` | Non-critical data, better perceived performance |
| `await useLazyFetch()` | Same as lazy, await only ensures initialization |
| `await useLazyFetch()` | Same as lazy, await only ensures initialization |
## Avoid Double Fetching
@@ -72,7 +72,7 @@ const { data, status } = useLazyFetch('/api/posts')
```vue
<script setup lang="ts">
// This fetches TWICE: once on server, once on client
const data = await $fetch('/api/posts')
const data = await $fetch('/api/posts');
</script>
```
@@ -81,7 +81,7 @@ const data = await $fetch('/api/posts')
```vue
<script setup lang="ts">
// Fetches on server, hydrates on client (no double fetch)
const { data } = await useFetch('/api/posts')
const { data } = await useFetch('/api/posts');
</script>
```
@@ -92,7 +92,7 @@ const { data } = await useFetch('/api/posts')
```vue
<script setup lang="ts">
// Key is auto-generated from file/line - can cause issues
const { data } = await useAsyncData(() => fetchPosts())
const { data } = await useAsyncData(() => fetchPosts());
</script>
```
@@ -101,17 +101,13 @@ const { data } = await useAsyncData(() => fetchPosts())
```vue
<script setup lang="ts">
// Explicit key for predictable caching
const { data } = await useAsyncData(
'posts',
() => fetchPosts(),
)
const { data } = await useAsyncData('posts', () => fetchPosts());
// Dynamic keys for parameterized data
const route = useRoute()
const { data: post } = await useAsyncData(
`post-${route.params.id}`,
() => fetchPost(route.params.id),
)
const route = useRoute();
const { data: post } = await useAsyncData(`post-${route.params.id}`, () =>
fetchPost(route.params.id),
);
</script>
```
@@ -119,7 +115,7 @@ const { data: post } = await useAsyncData(
```vue
<script setup lang="ts">
const { data, status, error } = await useFetch('/api/posts')
const { data, status, error } = await useFetch('/api/posts');
</script>
<template>
@@ -139,18 +135,18 @@ const { data, status, error } = await useFetch('/api/posts')
```vue
<script setup lang="ts">
const id = useRoute().params.id
const id = useRoute().params.id;
// Critical data - blocks navigation
const { data: post } = await useFetch(`/api/posts/${id}`)
const { data: post } = await useFetch(`/api/posts/${id}`);
// Non-critical data - doesn't block navigation
const { data: comments, status } = useFetch(`/api/posts/${id}/comments`, {
lazy: true,
})
});
// Or use useLazyFetch
const { data: related } = useLazyFetch(`/api/posts/${id}/related`)
const { data: related } = useLazyFetch(`/api/posts/${id}/related`);
</script>
<template>
@@ -173,7 +169,7 @@ const { data: related } = useLazyFetch(`/api/posts/${id}/related`)
const { data } = await useFetch('/api/users', {
// Only include these fields in payload
pick: ['id', 'name', 'avatar'],
})
});
</script>
```
@@ -183,14 +179,14 @@ const { data } = await useFetch('/api/users', {
<script setup lang="ts">
const { data } = await useFetch('/api/posts', {
transform: (posts) => {
return posts.map(post => ({
return posts.map((post) => ({
id: post.id,
title: post.title,
excerpt: post.content.slice(0, 100),
date: new Date(post.createdAt).toLocaleDateString(),
}))
}));
},
})
});
</script>
```
@@ -200,17 +196,14 @@ const { data } = await useFetch('/api/posts', {
```vue
<script setup lang="ts">
const { data } = await useAsyncData(
'dashboard',
async (_nuxtApp, { signal }) => {
const [user, posts, stats] = await Promise.all([
$fetch('/api/user', { signal }),
$fetch('/api/posts', { signal }),
$fetch('/api/stats', { signal }),
])
return { user, posts, stats }
},
)
const { data } = await useAsyncData('dashboard', async (_nuxtApp, { signal }) => {
const [user, posts, stats] = await Promise.all([
$fetch('/api/user', { signal }),
$fetch('/api/posts', { signal }),
$fetch('/api/stats', { signal }),
]);
return { user, posts, stats };
});
</script>
```
@@ -222,7 +215,7 @@ const { data } = await useAsyncData(
const [{ data: user }, { data: posts }] = await Promise.all([
useFetch('/api/user'),
useFetch('/api/posts'),
])
]);
</script>
```
@@ -232,14 +225,14 @@ const [{ data: user }, { data: posts }] = await Promise.all([
```vue
<script setup lang="ts">
const page = ref(1)
const category = ref('all')
const page = ref(1);
const category = ref('all');
const { data } = await useFetch('/api/posts', {
query: { page, category },
// Auto-refresh when these change
watch: [page, category],
})
});
</script>
```
@@ -247,10 +240,10 @@ const { data } = await useFetch('/api/posts', {
```vue
<script setup lang="ts">
const { data, refresh, status } = await useFetch('/api/posts')
const { data, refresh, status } = await useFetch('/api/posts');
async function refreshPosts() {
await refresh()
await refresh();
}
</script>
```
@@ -259,16 +252,16 @@ async function refreshPosts() {
```vue
<script setup lang="ts">
const userId = ref<string | null>(null)
const userId = ref<string | null>(null);
const { data, execute } = useFetch(() => `/api/users/${userId.value}`, {
immediate: false, // Don't fetch until userId is set
})
});
// Later, when userId is available
function loadUser(id: string) {
userId.value = id
execute()
userId.value = id;
execute();
}
</script>
```
@@ -282,7 +275,7 @@ const { data } = await useFetch('/api/static-content', {
server: true,
lazy: true,
getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key],
})
});
</script>
```
@@ -290,15 +283,19 @@ const { data } = await useFetch('/api/static-content', {
```vue
<script setup lang="ts">
const { data, error, refresh } = await useFetch('/api/posts')
const { data, error, refresh } = await useFetch('/api/posts');
// Watch for errors if need event-like handling
watch(error, (err) => {
if (err) {
console.error('Fetch failed:', err)
// Show toast, redirect, etc.
}
}, { immediate: true })
watch(
error,
(err) => {
if (err) {
console.error('Fetch failed:', err);
// Show toast, redirect, etc.
}
},
{ immediate: true },
);
</script>
<template>
@@ -314,16 +311,16 @@ watch(error, (err) => {
```vue
<!-- ComponentA.vue -->
<script setup lang="ts">
const { data } = await useFetch('/api/user', { key: 'current-user' })
const { data } = await useFetch('/api/user', { key: 'current-user' });
</script>
<!-- ComponentB.vue -->
<script setup lang="ts">
// Access cached data without refetching
const { data: user } = useNuxtData('current-user')
const { data: user } = useNuxtData('current-user');
// Or refresh it
const { refresh } = await useFetch('/api/user', { key: 'current-user' })
const { refresh } = await useFetch('/api/user', { key: 'current-user' });
</script>
```
@@ -334,7 +331,7 @@ const { refresh } = await useFetch('/api/user', { key: 'current-user' })
```vue
<script setup lang="ts">
// Don't trigger Pinia actions or side effects
await useAsyncData(() => store.fetchUser()) // Can cause issues
await useAsyncData(() => store.fetchUser()); // Can cause issues
</script>
```
@@ -343,12 +340,12 @@ await useAsyncData(() => store.fetchUser()) // Can cause issues
```vue
<script setup lang="ts">
await callOnce(async () => {
await store.fetchUser()
})
await store.fetchUser();
});
</script>
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/data-fetching
- https://nuxt.com/docs/api/composables/use-fetch
@@ -16,10 +16,10 @@ This error occurs when calling Nuxt composables outside the proper context.
```ts
// composables/bad.ts
// Called at module level - no Nuxt context!
const config = useRuntimeConfig()
const config = useRuntimeConfig();
export function useMyComposable() {
return config.public.apiBase
return config.public.apiBase;
}
```
@@ -29,14 +29,15 @@ export function useMyComposable() {
// composables/good.ts
export function useMyComposable() {
// Called inside the composable - has context
const config = useRuntimeConfig()
return config.public.apiBase
const config = useRuntimeConfig();
return config.public.apiBase;
}
```
### Valid Contexts for Composables
Nuxt composables work in:
- `<script setup>` blocks
- `setup()` function
- `defineNuxtPlugin()` callbacks
@@ -45,13 +46,13 @@ Nuxt composables work in:
```ts
// ✅ Plugin
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig() // Works
})
const config = useRuntimeConfig(); // Works
});
// ✅ Middleware
export default defineNuxtRouteMiddleware(() => {
const route = useRoute() // Works
})
const route = useRoute(); // Works
});
```
## Avoid State Leaks Between Requests
@@ -61,10 +62,10 @@ export default defineNuxtRouteMiddleware(() => {
```ts
// composables/bad.ts
// This state is SHARED between all requests on server!
const globalState = ref({ user: null })
const globalState = ref({ user: null });
export function useUser() {
return globalState
return globalState;
}
```
@@ -74,13 +75,14 @@ export function useUser() {
// composables/good.ts
export function useUser() {
// useState creates request-isolated state
return useState('user', () => ({ user: null }))
return useState('user', () => ({ user: null }));
}
```
### Why This Matters
On the server, module-level state persists across requests, causing:
- Data leaking between users
- Security vulnerabilities
- Memory leaks
@@ -94,7 +96,7 @@ Hydration mismatches occur when server HTML differs from client render.
```vue
<script setup>
// localStorage doesn't exist on server!
const theme = localStorage.getItem('theme') || 'light'
const theme = localStorage.getItem('theme') || 'light';
</script>
```
@@ -103,7 +105,7 @@ const theme = localStorage.getItem('theme') || 'light'
```vue
<script setup>
// useCookie works on both server and client
const theme = useCookie('theme', { default: () => 'light' })
const theme = useCookie('theme', { default: () => 'light' });
</script>
```
@@ -121,7 +123,7 @@ const theme = useCookie('theme', { default: () => 'light' })
```vue
<script setup>
// Value is generated once on server, hydrated on client
const randomValue = useState('random', () => Math.random())
const randomValue = useState('random', () => Math.random());
</script>
<template>
@@ -162,7 +164,7 @@ const randomValue = useState('random', () => Math.random())
<script setup>
if (import.meta.client) {
// Only runs in browser
window.addEventListener('scroll', handleScroll)
window.addEventListener('scroll', handleScroll);
}
</script>
```
@@ -186,9 +188,9 @@ onMounted(() => {
```vue
<script setup>
onMounted(async () => {
const { Chart } = await import('chart.js')
new Chart(canvas.value, config)
})
const { Chart } = await import('chart.js');
new Chart(canvas.value, config);
});
</script>
```
@@ -200,7 +202,7 @@ onMounted(async () => {
<script setup>
if (import.meta.server) {
// Only runs on server
const secrets = useRuntimeConfig().apiSecret
const secrets = useRuntimeConfig().apiSecret;
}
</script>
```
@@ -211,7 +213,7 @@ if (import.meta.server) {
<!-- components/ServerData.server.vue -->
<script setup>
// This entire component only runs on server
const data = await fetchSensitiveData()
const data = await fetchSensitiveData();
</script>
<template>
@@ -225,8 +227,8 @@ const data = await fetchSensitiveData()
```vue
<script setup>
await someAsyncOperation()
const route = useRoute() // May fail - context lost after await
await someAsyncOperation();
const route = useRoute(); // May fail - context lost after await
</script>
```
@@ -235,10 +237,10 @@ const route = useRoute() // May fail - context lost after await
```vue
<script setup>
// Get all composables before any await
const route = useRoute()
const config = useRuntimeConfig()
const route = useRoute();
const config = useRuntimeConfig();
await someAsyncOperation()
await someAsyncOperation();
// Now safe to use route and config
</script>
```
@@ -251,8 +253,8 @@ await someAsyncOperation()
// plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
// Only runs on client
initAnalytics()
})
initAnalytics();
});
```
### Server-only Plugins
@@ -261,8 +263,8 @@ export default defineNuxtPlugin(() => {
// plugins/server-init.server.ts
export default defineNuxtPlugin(() => {
// Only runs on server
initServerConnections()
})
initServerConnections();
});
```
### Provide/Inject Pattern
@@ -270,20 +272,20 @@ export default defineNuxtPlugin(() => {
```ts
// plugins/api.ts
export default defineNuxtPlugin(() => {
const api = createApiClient()
const api = createApiClient();
return {
provide: {
api,
},
}
})
};
});
```
```vue
<script setup>
const { $api } = useNuxtApp()
const data = await $api.get('/users')
const { $api } = useNuxtApp();
const data = await $api.get('/users');
</script>
```
@@ -293,7 +295,7 @@ const data = await $api.get('/users')
```vue
<script setup>
import SomeLibrary from 'browser-only-lib' // Breaks SSR
import SomeLibrary from 'browser-only-lib'; // Breaks SSR
</script>
```
@@ -329,8 +331,8 @@ onMounted(async () => {
```vue
<script setup>
console.log('Server:', import.meta.server)
console.log('Client:', import.meta.client)
console.log('Server:', import.meta.server);
console.log('Client:', import.meta.client);
</script>
```
@@ -340,14 +342,14 @@ DevTools shows payload data and hydration state.
### Common Error Messages
| Error | Cause |
|-------|-------|
| Error | Cause |
| --------------------------- | --------------------------------------- |
| "Nuxt instance unavailable" | Composable called outside setup context |
| "Hydration mismatch" | Server/client HTML differs |
| "window is not defined" | Browser API used during SSR |
| "document is not defined" | DOM access during SSR |
| "Hydration mismatch" | Server/client HTML differs |
| "window is not defined" | Browser API used during SSR |
| "document is not defined" | DOM access during SSR |
<!--
<!--
Source references:
- https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables
- https://nuxt.com/docs/guide/best-practices/hydration
@@ -76,6 +76,7 @@ npx nuxt dev --tunnel
| `--clear` | Clear console on restart |
**Environment Variables:**
- `NUXT_PORT` or `PORT` - Default port
- `NUXT_HOST` or `HOST` - Default host
@@ -254,7 +255,7 @@ npx nuxt dev --extends ./base-layer
npx nuxt build --extends ./base-layer
```
<!--
<!--
Source references:
- https://nuxt.com/docs/api/commands/dev
- https://nuxt.com/docs/api/commands/build
@@ -17,7 +17,7 @@ export default defineNuxtConfig({
// Configuration options
devtools: { enabled: true },
modules: ['@nuxt/ui'],
})
});
```
### Environment Overrides
@@ -39,7 +39,7 @@ export default defineNuxtConfig({
// Staging environment config
},
},
})
});
```
Use `--envName` flag to select environment: `nuxt build --envName staging`
@@ -59,7 +59,7 @@ export default defineNuxtConfig({
apiBase: '/api',
},
},
})
});
```
Override with environment variables:
@@ -74,7 +74,7 @@ Access in components/composables:
```vue
<script setup lang="ts">
const config = useRuntimeConfig()
const config = useRuntimeConfig();
// Server: config.apiSecret, config.public.apiBase
// Client: config.public.apiBase only
</script>
@@ -94,26 +94,26 @@ export default defineAppConfig({
primary: '#ff0000',
},
},
})
});
```
Access in components:
```vue
<script setup lang="ts">
const appConfig = useAppConfig()
const appConfig = useAppConfig();
</script>
```
## runtimeConfig vs app.config
| Feature | runtimeConfig | app.config |
|---------|--------------|------------|
| Client-side | Hydrated | Bundled |
| Environment variables | Yes | No |
| Reactive | Yes | Yes |
| Hot module replacement | No | Yes |
| Non-primitive JS types | No | Yes |
| Feature | runtimeConfig | app.config |
| ---------------------- | ------------- | ---------- |
| Client-side | Hydrated | Bundled |
| Environment variables | Yes | No |
| Reactive | Yes | Yes |
| Hot module replacement | No | Yes |
| Non-primitive JS types | No | Yes |
**Use runtimeConfig** for secrets and values that change per environment.
**Use app.config** for public tokens, theme settings, and non-sensitive config.
@@ -139,7 +139,7 @@ export default defineNuxtConfig({
postcss: {
// postcss options
},
})
});
```
## Vue Configuration
@@ -151,10 +151,10 @@ export default defineNuxtConfig({
vue: {
propsDestructure: true,
},
})
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/configuration
- https://nuxt.com/docs/guide/going-further/runtime-config
@@ -19,7 +19,7 @@ Primary composable for fetching data in components:
```vue
<script setup lang="ts">
const { data, status, error, refresh, clear } = await useFetch('/api/posts')
const { data, status, error, refresh, clear } = await useFetch('/api/posts');
</script>
<template>
@@ -46,7 +46,7 @@ const { data } = await useFetch('/api/posts', {
// Only pick specific fields
pick: ['id', 'title'],
// Transform response
transform: (posts) => posts.map(p => ({ ...p, slug: slugify(p.title) })),
transform: (posts) => posts.map((p) => ({ ...p, slug: slugify(p.title) })),
// Custom key for caching
key: 'posts-list',
// Don't fetch on server
@@ -57,17 +57,17 @@ const { data } = await useFetch('/api/posts', {
immediate: false,
// Default value
default: () => [],
})
});
```
### Reactive Parameters
```vue
<script setup lang="ts">
const page = ref(1)
const page = ref(1);
const { data } = await useFetch('/api/posts', {
query: { page }, // Automatically refetches when page changes
})
});
</script>
```
@@ -75,8 +75,8 @@ const { data } = await useFetch('/api/posts', {
```vue
<script setup lang="ts">
const id = ref(1)
const { data } = await useFetch(() => `/api/posts/${id.value}`)
const id = ref(1);
const { data } = await useFetch(() => `/api/posts/${id.value}`);
// Refetches when id changes
</script>
```
@@ -88,8 +88,8 @@ For wrapping any async function:
```vue
<script setup lang="ts">
const { data, error } = await useAsyncData('user', () => {
return myCustomFetch('/user/profile')
})
return myCustomFetch('/user/profile');
});
</script>
```
@@ -98,12 +98,9 @@ const { data, error } = await useAsyncData('user', () => {
```vue
<script setup lang="ts">
const { data } = await useAsyncData('cart', async () => {
const [coupons, offers] = await Promise.all([
$fetch('/api/coupons'),
$fetch('/api/offers'),
])
return { coupons, offers }
})
const [coupons, offers] = await Promise.all([$fetch('/api/coupons'), $fetch('/api/offers')]);
return { coupons, offers };
});
</script>
```
@@ -117,7 +114,7 @@ async function submitForm() {
const result = await $fetch('/api/submit', {
method: 'POST',
body: { name: 'John' },
})
});
}
</script>
```
@@ -128,14 +125,14 @@ async function submitForm() {
All composables return:
| Property | Type | Description |
|----------|------|-------------|
| `data` | `Ref<T>` | Fetched data |
| `error` | `Ref<Error>` | Error if request failed |
| `status` | `Ref<'idle' \| 'pending' \| 'success' \| 'error'>` | Request status |
| `refresh` | `() => Promise` | Refetch data |
| `execute` | `() => Promise` | Alias for refresh |
| `clear` | `() => void` | Reset data and error |
| Property | Type | Description |
| --------- | -------------------------------------------------- | ----------------------- |
| `data` | `Ref<T>` | Fetched data |
| `error` | `Ref<Error>` | Error if request failed |
| `status` | `Ref<'idle' \| 'pending' \| 'success' \| 'error'>` | Request status |
| `refresh` | `() => Promise` | Refetch data |
| `execute` | `() => Promise` | Alias for refresh |
| `clear` | `() => void` | Reset data and error |
## Lazy Fetching
@@ -144,11 +141,11 @@ Don't block navigation:
```vue
<script setup lang="ts">
// Using lazy option
const { data, status } = await useFetch('/api/posts', { lazy: true })
const { data, status } = await useFetch('/api/posts', { lazy: true });
// Or use lazy variants
const { data, status } = await useLazyFetch('/api/posts')
const { data, status } = await useLazyAsyncData('key', fetchFn)
const { data, status } = await useLazyFetch('/api/posts');
const { data, status } = await useLazyAsyncData('key', fetchFn);
</script>
```
@@ -156,16 +153,16 @@ const { data, status } = await useLazyAsyncData('key', fetchFn)
```vue
<script setup lang="ts">
const category = ref('tech')
const category = ref('tech');
const { data, refresh } = await useFetch('/api/posts', {
query: { category },
// Auto-refresh when category changes
watch: [category],
})
});
// Manual refresh
const refreshData = () => refresh()
const refreshData = () => refresh();
</script>
```
@@ -176,10 +173,10 @@ Data is cached by key. Share data across components:
```vue
<script setup lang="ts">
// In component A
const { data } = await useFetch('/api/user', { key: 'current-user' })
const { data } = await useFetch('/api/user', { key: 'current-user' });
// In component B - uses cached data
const { data } = useNuxtData('current-user')
const { data } = useNuxtData('current-user');
</script>
```
@@ -187,13 +184,13 @@ Refresh cached data globally:
```ts
// Refresh specific key
await refreshNuxtData('current-user')
await refreshNuxtData('current-user');
// Refresh all data
await refreshNuxtData()
await refreshNuxtData();
// Clear cached data
clearNuxtData('current-user')
clearNuxtData('current-user');
```
## Interceptors
@@ -201,20 +198,20 @@ clearNuxtData('current-user')
```ts
const { data } = await useFetch('/api/auth', {
onRequest({ options }) {
options.headers.set('Authorization', `Bearer ${token}`)
options.headers.set('Authorization', `Bearer ${token}`);
},
onRequestError({ error }) {
console.error('Request failed:', error)
console.error('Request failed:', error);
},
onResponse({ response }) {
// Process response
},
onResponseError({ response }) {
if (response.status === 401) {
navigateTo('/login')
navigateTo('/login');
}
},
})
});
```
## Passing Headers (SSR)
@@ -223,12 +220,12 @@ const { data } = await useFetch('/api/auth', {
```vue
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
const data = await $fetch('/api/user', { headers })
const headers = useRequestHeaders(['cookie']);
const data = await $fetch('/api/user', { headers });
</script>
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/data-fetching
- https://nuxt.com/docs/api/composables/use-fetch
@@ -22,6 +22,7 @@ node .output/server/index.mjs
```
Environment variables:
- `PORT` or `NITRO_PORT` (default: 3000)
- `HOST` or `NITRO_HOST` (default: 0.0.0.0)
@@ -42,7 +43,7 @@ export default defineNuxtConfig({
nitro: {
preset: 'vercel', // or 'netlify', 'cloudflare-pages', etc.
},
})
});
```
Or via environment variable:
@@ -70,6 +71,7 @@ vercel
```
**Pros:**
- Zero configuration for Nuxt (auto-detects)
- Excellent preview deployments for PRs
- Built-in analytics and speed insights
@@ -77,6 +79,7 @@ vercel
- Great free tier for personal projects
**Cons:**
- Can get expensive at scale (bandwidth costs)
- Vendor lock-in concerns
- Limited build minutes on free tier
@@ -98,6 +101,7 @@ netlify deploy --prod
```
**Pros:**
- Great free tier with generous bandwidth
- Built-in forms, identity, and functions
- Excellent for static sites with some dynamic features
@@ -105,6 +109,7 @@ netlify deploy --prod
- Split testing built-in
**Cons:**
- SSR/serverless functions can be slower than Vercel
- Less optimized for full SSR apps
- Build minutes can run out on free tier
@@ -123,6 +128,7 @@ NITRO_PRESET=cloudflare-pages nuxt build
```
**Pros:**
- Unlimited bandwidth on free tier
- Excellent global edge network (fastest TTFB)
- Workers for edge computing
@@ -130,6 +136,7 @@ NITRO_PRESET=cloudflare-pages nuxt build
- D1, KV, R2 for data storage
**Cons:**
- Workers have execution limits (CPU time)
- Some Node.js APIs not available in Workers
- Less mature than Vercel/Netlify for frameworks
@@ -157,16 +164,17 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
# Deploy to your server (example: rsync to VPS)
- name: Deploy to server
run: rsync -avz .output/ user@server:/app/
```
**Pros:**
- Full control over build and deployment
- No vendor lock-in
- Can deploy anywhere (VPS, Docker, Kubernetes)
@@ -174,6 +182,7 @@ jobs:
- Customizable workflows
**Cons:**
- Requires more setup and maintenance
- Need to manage your own infrastructure
- No built-in preview deployments
@@ -185,14 +194,14 @@ jobs:
## Quick Decision Guide
| Need | Recommendation |
|------|----------------|
| Fastest setup, small team | **Vercel** |
| Static site with forms | **Netlify** |
| Cost-sensitive at scale | **Cloudflare Pages** |
| Full control / enterprise | **GitHub Actions + VPS** |
| Docker/Kubernetes | **GitHub Actions + Container Registry** |
| Serverless APIs | **Vercel** or **AWS Lambda** |
| Need | Recommendation |
| ------------------------- | --------------------------------------- |
| Fastest setup, small team | **Vercel** |
| Static site with forms | **Netlify** |
| Cost-sensitive at scale | **Cloudflare Pages** |
| Full control / enterprise | **GitHub Actions + VPS** |
| Docker/Kubernetes | **GitHub Actions + Container Registry** |
| Serverless APIs | **Vercel** or **AWS Lambda** |
## Docker Deployment
@@ -217,7 +226,7 @@ docker build -t my-nuxt-app .
docker run -p 3000:3000 my-nuxt-app
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/deployment
- https://nitro.build/deploy
@@ -48,7 +48,7 @@ Contains all application code. Can also be at root level (without `app/` folder)
// nuxt.config.ts - customize source directory
export default defineNuxtConfig({
srcDir: 'src/', // Change from 'app/' to 'src/'
})
});
```
### `app/components/`
@@ -99,7 +99,7 @@ Re-export nested composables:
```ts
// composables/index.ts
export { useHelper } from './nested/utils'
export { useHelper } from './nested/utils';
```
### `app/pages/`
@@ -153,7 +153,7 @@ Use in pages:
definePageMeta({
layout: 'admin',
// layout: false // Disable layout
})
});
</script>
```
@@ -240,28 +240,28 @@ Reference in components:
## Special Files
| File | Purpose |
|------|---------|
| `app.vue` | Root component (optional with pages/) |
| `app.config.ts` | Runtime app configuration |
| `error.vue` | Custom error page |
| `nuxt.config.ts` | Build-time configuration |
| `.nuxtignore` | Ignore files from Nuxt |
| `.env` | Environment variables |
| File | Purpose |
| ---------------- | ------------------------------------- |
| `app.vue` | Root component (optional with pages/) |
| `app.config.ts` | Runtime app configuration |
| `error.vue` | Custom error page |
| `nuxt.config.ts` | Build-time configuration |
| `.nuxtignore` | Ignore files from Nuxt |
| `.env` | Environment variables |
## File Naming Conventions
| Pattern | Meaning |
|---------|---------|
| `[param]` | Dynamic route parameter |
| `[[param]]` | Optional parameter |
| `[...slug]` | Catch-all route |
| `(group)` | Route group (not in URL) |
| `.client.vue` | Client-only component |
| `.server.vue` | Server-only component |
| `.global.ts` | Global middleware |
| Pattern | Meaning |
| ------------- | ------------------------ |
| `[param]` | Dynamic route parameter |
| `[[param]]` | Optional parameter |
| `[...slug]` | Catch-all route |
| `(group)` | Route group (not in URL) |
| `.client.vue` | Client-only component |
| `.server.vue` | Server-only component |
| `.global.ts` | Global middleware |
<!--
<!--
Source references:
- https://nuxt.com/docs/directory-structure
- https://nuxt.com/docs/directory-structure/app
@@ -21,12 +21,12 @@ export default defineNuxtConfig({
'./modules/my-module',
// Inline module
(options, nuxt) => {
console.log('Inline module')
console.log('Inline module');
},
// With options
['@nuxt/image', { provider: 'cloudinary' }],
],
})
});
```
## Creating Modules
@@ -44,76 +44,76 @@ export default defineNuxtModule({
enabled: true,
},
setup(options, nuxt) {
if (!options.enabled) return
if (!options.enabled) return;
console.log('My module is running!')
console.log('My module is running!');
},
})
});
```
### Adding Components
```ts
// modules/ui/index.ts
import { addComponent, createResolver } from '@nuxt/kit'
import { addComponent, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Add single component
addComponent({
name: 'MyButton',
filePath: resolve('./runtime/components/MyButton.vue'),
})
});
// Add components directory
addComponentsDir({
path: resolve('./runtime/components'),
prefix: 'My',
})
});
},
})
});
```
### Adding Composables
```ts
// modules/utils/index.ts
import { addImports, createResolver } from '@nuxt/kit'
import { addImports, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
// Add auto-imported composable
addImports({
name: 'useMyUtil',
from: resolve('./runtime/composables/useMyUtil'),
})
});
// Add directory for auto-imports
addImportsDir(resolve('./runtime/composables'))
addImportsDir(resolve('./runtime/composables'));
},
})
});
```
### Adding Plugins
```ts
// modules/analytics/index.ts
import { addPlugin, createResolver } from '@nuxt/kit'
import { addPlugin, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
addPlugin({
src: resolve('./runtime/plugin'),
mode: 'client', // 'client', 'server', or 'all'
})
});
},
})
});
```
Plugin file:
@@ -122,27 +122,27 @@ Plugin file:
// modules/analytics/runtime/plugin.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:finish', () => {
console.log('Page loaded')
})
})
console.log('Page loaded');
});
});
```
### Adding Server Routes
```ts
// modules/api/index.ts
import { addServerHandler, createResolver } from '@nuxt/kit'
import { addServerHandler, createResolver } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const { resolve } = createResolver(import.meta.url)
const { resolve } = createResolver(import.meta.url);
addServerHandler({
route: '/api/my-endpoint',
handler: resolve('./runtime/server/api/my-endpoint'),
})
});
},
})
});
```
### Extending Config
@@ -152,19 +152,19 @@ export default defineNuxtModule({
export default defineNuxtModule({
setup(options, nuxt) {
// Add CSS
nuxt.options.css.push('my-module/styles.css')
nuxt.options.css.push('my-module/styles.css');
// Add runtime config
nuxt.options.runtimeConfig.public.myModule = {
apiUrl: options.apiUrl,
}
};
// Extend Vite config
nuxt.options.vite.optimizeDeps ||= {}
nuxt.options.vite.optimizeDeps.include ||= []
nuxt.options.vite.optimizeDeps.include.push('some-package')
nuxt.options.vite.optimizeDeps ||= {};
nuxt.options.vite.optimizeDeps.include ||= [];
nuxt.options.vite.optimizeDeps.include.push('some-package');
},
})
});
```
## Module Hooks
@@ -174,26 +174,26 @@ export default defineNuxtModule({
setup(options, nuxt) {
// Build-time hooks
nuxt.hook('modules:done', () => {
console.log('All modules loaded')
})
console.log('All modules loaded');
});
nuxt.hook('components:dirs', (dirs) => {
dirs.push({ path: '~/extra-components' })
})
dirs.push({ path: '~/extra-components' });
});
nuxt.hook('pages:extend', (pages) => {
pages.push({
name: 'custom-page',
path: '/custom',
file: resolve('./runtime/pages/custom.vue'),
})
})
});
});
nuxt.hook('imports:extend', (imports) => {
imports.push({ name: 'myHelper', from: 'my-package' })
})
imports.push({ name: 'myHelper', from: 'my-package' });
});
},
})
});
```
## Module Options
@@ -202,9 +202,9 @@ Type-safe options with defaults:
```ts
export interface ModuleOptions {
apiKey: string
enabled?: boolean
prefix?: string
apiKey: string;
enabled?: boolean;
prefix?: string;
}
export default defineNuxtModule<ModuleOptions>({
@@ -219,10 +219,10 @@ export default defineNuxtModule<ModuleOptions>({
setup(options, nuxt) {
// options is typed as ModuleOptions
if (!options.apiKey) {
console.warn('API key not provided')
console.warn('API key not provided');
}
},
})
});
```
Usage:
@@ -235,7 +235,7 @@ export default defineNuxtConfig({
apiKey: 'xxx',
prefix: 'Custom',
},
})
});
```
## Local Modules
@@ -260,7 +260,7 @@ export default defineNuxtConfig({
modules: [
'~/modules/my-module', // Explicit
],
})
});
```
## Module Dependencies
@@ -281,10 +281,10 @@ export default defineNuxtModule({
setup() {
// @nuxt/image is guaranteed to be installed
},
})
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/guide/modules
- https://nuxt.com/docs/guide/modules/module-anatomy
@@ -35,9 +35,9 @@ Access route parameters:
```vue
<script setup lang="ts">
const route = useRoute()
const route = useRoute();
// /posts/123 → route.params.id = '123'
console.log(route.params.id)
console.log(route.params.id);
</script>
```
@@ -61,12 +61,12 @@ NuxtLink automatically prefetches linked pages when they enter the viewport.
```vue
<script setup lang="ts">
const router = useRouter()
const router = useRouter();
function goToPost(id: number) {
navigateTo(`/posts/${id}`)
navigateTo(`/posts/${id}`);
// or
router.push({ name: 'posts-id', params: { id } })
router.push({ name: 'posts-id', params: { id } });
}
</script>
```
@@ -78,12 +78,12 @@ function goToPost(id: number) {
```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const isAuthenticated = false // Your auth logic
const isAuthenticated = false; // Your auth logic
if (!isAuthenticated) {
return navigateTo('/login')
return navigateTo('/login');
}
})
});
```
Apply to pages:
@@ -93,7 +93,7 @@ Apply to pages:
definePageMeta({
middleware: 'auth',
// or multiple: middleware: ['auth', 'admin']
})
});
</script>
```
@@ -104,8 +104,8 @@ Name files with `.global` suffix:
```ts
// middleware/logging.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
console.log('Navigating to:', to.path)
})
console.log('Navigating to:', to.path);
});
```
### Inline Middleware
@@ -118,7 +118,7 @@ definePageMeta({
// Inline middleware logic
},
],
})
});
</script>
```
@@ -134,9 +134,9 @@ definePageMeta({
middleware: 'auth',
validate: (route) => {
// Return false for 404, or object with status/statusText
return /^\d+$/.test(route.params.id as string)
return /^\d+$/.test(route.params.id as string);
},
})
});
</script>
```
@@ -147,9 +147,9 @@ definePageMeta({
definePageMeta({
validate: (route) => {
// Must return boolean or object with status
return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id);
},
})
});
</script>
```
@@ -186,7 +186,7 @@ Use in pages:
<script setup lang="ts">
definePageMeta({
layout: 'admin',
})
});
</script>
```
@@ -194,10 +194,10 @@ Dynamic layout:
```vue
<script setup lang="ts">
const layout = ref('default')
const layout = ref('default');
function enableAdmin() {
setPageLayout('admin')
setPageLayout('admin');
}
</script>
```
@@ -208,17 +208,17 @@ function enableAdmin() {
<script setup lang="ts">
onBeforeRouteLeave((to, from) => {
// Confirm before leaving
const answer = window.confirm('Leave?')
if (!answer) return false
})
const answer = window.confirm('Leave?');
if (!answer) return false;
});
onBeforeRouteUpdate((to, from) => {
// Called when route changes but component is reused
})
});
</script>
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/routing
- https://nuxt.com/docs/directory-structure/app/pages
@@ -55,10 +55,11 @@ export default defineNuxtConfig({
pathPrefix: false, // Use filename only
},
],
})
});
```
With `pathPrefix: false`:
```
components/base/Button.vue → <Button />
```
@@ -69,7 +70,7 @@ Prefix with `Lazy` for dynamic imports:
```vue
<script setup lang="ts">
const showChart = ref(false)
const showChart = ref(false);
</script>
<template>
@@ -80,6 +81,7 @@ const showChart = ref(false)
```
Benefits:
- Reduces initial bundle size
- Code-splits component into separate chunk
- Loads on-demand
@@ -148,7 +150,7 @@ Hydrate on condition:
```vue
<script setup lang="ts">
const isReady = ref(false)
const isReady = ref(false);
</script>
<template>
@@ -175,7 +177,7 @@ Never hydrate (static only):
<script setup>
function onChartReady() {
console.log('Chart is now interactive')
console.log('Chart is now interactive');
}
</script>
```
@@ -218,7 +220,7 @@ export default defineNuxtConfig({
experimental: {
componentIslands: true,
},
})
});
```
### Paired Components
@@ -235,9 +237,9 @@ Server version renders during SSR, client version takes over after hydration.
```vue
<script setup lang="ts">
import { SomeComponent } from '#components'
import { SomeComponent } from '#components';
const dynamicComponent = resolveComponent('MyButton')
const dynamicComponent = resolveComponent('MyButton');
</script>
<template>
@@ -252,7 +254,7 @@ Bypass auto-imports when needed:
```vue
<script setup lang="ts">
import { LazyMountainsList, NuxtLink } from '#components'
import { LazyMountainsList, NuxtLink } from '#components';
</script>
```
@@ -266,7 +268,7 @@ export default defineNuxtConfig({
{ path: '~/components/forms', prefix: 'Form' },
'~/components', // Default, should come last
],
})
});
```
## Global Components
@@ -280,7 +282,7 @@ export default defineNuxtConfig({
global: true,
dirs: ['~/components'],
},
})
});
```
Or use `.global.vue` suffix:
@@ -298,7 +300,7 @@ export default defineNuxtConfig({
components: {
dirs: [], // Disable auto-imports
},
})
});
```
## Library Authors
@@ -307,21 +309,21 @@ Register components from npm package:
```ts
// my-ui-lib/nuxt.ts
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit';
export default defineNuxtModule({
setup() {
const resolver = createResolver(import.meta.url)
const resolver = createResolver(import.meta.url);
addComponentsDir({
path: resolver.resolve('./components'),
prefix: 'MyUi',
})
});
},
})
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/directory-structure/app/components
- https://nuxt.com/docs/guide/concepts/auto-imports#auto-imported-components
@@ -29,11 +29,7 @@ Optimized link component with prefetching:
<NuxtLink to="/page" replace>Replace</NuxtLink>
<!-- Custom active class -->
<NuxtLink
to="/dashboard"
active-class="text-primary"
exact-active-class="font-bold"
>
<NuxtLink to="/dashboard" active-class="text-primary" exact-active-class="font-bold">
Dashboard
</NuxtLink>
</template>
@@ -91,7 +87,7 @@ Dynamic layout:
</template>
<script setup>
const layout = computed(() => isAdmin ? 'admin' : 'default')
const layout = computed(() => (isAdmin ? 'admin' : 'default'));
</script>
```
@@ -144,7 +140,7 @@ Catch and handle errors in child components:
<script setup>
function handleError(error) {
console.error('Error caught:', error)
console.error('Error caught:', error);
}
</script>
```
@@ -205,10 +201,7 @@ Optimized images (requires `@nuxt/image` module):
/>
<!-- Art direction with picture -->
<NuxtPicture
src="/images/hero.jpg"
:img-attrs="{ alt: 'Hero image' }"
/>
<NuxtPicture src="/images/hero.jpg" :img-attrs="{ alt: 'Hero image' }" />
</template>
```
@@ -255,7 +248,7 @@ Accessibility: announces page changes to screen readers:
</template>
```
<!--
<!--
Source references:
- https://nuxt.com/docs/api/components/nuxt-link
- https://nuxt.com/docs/api/components/nuxt-page
@@ -14,25 +14,25 @@ Nuxt automatically imports Vue APIs, Nuxt composables, and your custom composabl
```vue
<script setup lang="ts">
// No imports needed - all auto-imported
const count = ref(0)
const doubled = computed(() => count.value * 2)
const count = ref(0);
const doubled = computed(() => count.value * 2);
watch(count, (newVal) => {
console.log('Count changed:', newVal)
})
console.log('Count changed:', newVal);
});
onMounted(() => {
console.log('Component mounted')
})
console.log('Component mounted');
});
// Lifecycle hooks
onBeforeMount(() => {})
onUnmounted(() => {})
onBeforeUnmount(() => {})
onBeforeMount(() => {});
onUnmounted(() => {});
onBeforeUnmount(() => {});
// Reactivity
const state = reactive({ name: 'John' })
const shallow = shallowRef({ deep: 'object' })
const state = reactive({ name: 'John' });
const shallow = shallowRef({ deep: 'object' });
</script>
```
@@ -41,28 +41,28 @@ const shallow = shallowRef({ deep: 'object' })
```vue
<script setup lang="ts">
// All auto-imported
const route = useRoute()
const router = useRouter()
const config = useRuntimeConfig()
const appConfig = useAppConfig()
const nuxtApp = useNuxtApp()
const route = useRoute();
const router = useRouter();
const config = useRuntimeConfig();
const appConfig = useAppConfig();
const nuxtApp = useNuxtApp();
// Data fetching
const { data } = await useFetch('/api/data')
const { data: asyncData } = await useAsyncData('key', () => fetchData())
const { data } = await useFetch('/api/data');
const { data: asyncData } = await useAsyncData('key', () => fetchData());
// State
const state = useState('key', () => 'initial')
const cookie = useCookie('token')
const state = useState('key', () => 'initial');
const cookie = useCookie('token');
// Head/SEO
useHead({ title: 'My Page' })
useSeoMeta({ description: 'Page description' })
useHead({ title: 'My Page' });
useSeoMeta({ description: 'Page description' });
// Request helpers (SSR)
const headers = useRequestHeaders()
const event = useRequestEvent()
const url = useRequestURL()
const headers = useRequestHeaders();
const event = useRequestEvent();
const url = useRequestURL();
</script>
```
@@ -73,32 +73,32 @@ const url = useRequestURL()
```ts
// composables/useCounter.ts
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
const count = ref(initial);
const increment = () => count.value++;
const decrement = () => count.value--;
return { count, increment, decrement };
}
```
```ts
// composables/useAuth.ts
export function useAuth() {
const user = useState<User | null>('user', () => null)
const isLoggedIn = computed(() => !!user.value)
const user = useState<User | null>('user', () => null);
const isLoggedIn = computed(() => !!user.value);
async function login(credentials: Credentials) {
user.value = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials,
})
});
}
async function logout() {
await $fetch('/api/auth/logout', { method: 'POST' })
user.value = null
await $fetch('/api/auth/logout', { method: 'POST' });
user.value = null;
}
return { user, isLoggedIn, login, logout }
return { user, isLoggedIn, login, logout };
}
```
@@ -107,8 +107,8 @@ export function useAuth() {
```vue
<script setup lang="ts">
// Auto-imported - no import statement needed
const { count, increment } = useCounter(10)
const { user, isLoggedIn, login } = useAuth()
const { count, increment } = useCounter(10);
const { user, isLoggedIn, login } = useAuth();
</script>
```
@@ -129,7 +129,7 @@ Re-export nested composables:
```ts
// composables/index.ts
export { useHelper } from './nested/helper'
export { useHelper } from './nested/helper';
```
Or configure scanning:
@@ -143,7 +143,7 @@ export default defineNuxtConfig({
'composables/**', // Scan all nested
],
},
})
});
```
## Utilities (`app/utils/`)
@@ -151,22 +151,22 @@ export default defineNuxtConfig({
```ts
// utils/format.ts
export function formatDate(date: Date) {
return date.toLocaleDateString()
return date.toLocaleDateString();
}
export function formatCurrency(amount: number) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
}).format(amount);
}
```
```vue
<script setup lang="ts">
// Auto-imported
const date = formatDate(new Date())
const price = formatCurrency(99.99)
const date = formatDate(new Date());
const price = formatCurrency(99.99);
</script>
```
@@ -175,21 +175,21 @@ const price = formatCurrency(99.99)
```ts
// server/utils/db.ts
export function useDb() {
return createDbConnection()
return createDbConnection();
}
// server/utils/auth.ts
export function verifyToken(token: string) {
return jwt.verify(token, process.env.JWT_SECRET)
return jwt.verify(token, process.env.JWT_SECRET);
}
```
```ts
// server/api/users.ts
export default defineEventHandler(() => {
const db = useDb() // Auto-imported
return db.query('SELECT * FROM users')
})
const db = useDb(); // Auto-imported
return db.query('SELECT * FROM users');
});
```
## Third-party Package Imports
@@ -213,7 +213,7 @@ export default defineNuxtConfig({
},
],
},
})
});
```
## Explicit Imports
@@ -222,7 +222,7 @@ Use `#imports` alias when needed:
```vue
<script setup lang="ts">
import { ref, computed, useFetch } from '#imports'
import { ref, computed, useFetch } from '#imports';
</script>
```
@@ -232,7 +232,7 @@ Nuxt composables must be called in valid context:
```ts
// ❌ Wrong - module level
const config = useRuntimeConfig()
const config = useRuntimeConfig();
export function useMyComposable() {}
```
@@ -240,12 +240,13 @@ export function useMyComposable() {}
```ts
// ✅ Correct - inside function
export function useMyComposable() {
const config = useRuntimeConfig()
return { apiBase: config.public.apiBase }
const config = useRuntimeConfig();
return { apiBase: config.public.apiBase };
}
```
**Valid contexts:**
- `<script setup>` block
- `setup()` function
- `defineNuxtPlugin()` callback
@@ -265,10 +266,10 @@ export default defineNuxtConfig({
imports: {
scan: false,
},
})
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/guide/concepts/auto-imports
- https://nuxt.com/docs/directory-structure/app/composables
@@ -14,8 +14,8 @@ Create files in `server/api/` directory:
```ts
// server/api/hello.ts
export default defineEventHandler((event) => {
return { message: 'Hello World' }
})
return { message: 'Hello World' };
});
```
Access at `/api/hello`.
@@ -25,27 +25,27 @@ Access at `/api/hello`.
```ts
// server/api/users.get.ts - GET /api/users
export default defineEventHandler(() => {
return getUsers()
})
return getUsers();
});
// server/api/users.post.ts - POST /api/users
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return createUser(body)
})
const body = await readBody(event);
return createUser(body);
});
// server/api/users/[id].put.ts - PUT /api/users/:id
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const body = await readBody(event)
return updateUser(id, body)
})
const id = getRouterParam(event, 'id');
const body = await readBody(event);
return updateUser(id, body);
});
// server/api/users/[id].delete.ts - DELETE /api/users/:id
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return deleteUser(id)
})
const id = getRouterParam(event, 'id');
return deleteUser(id);
});
```
### Route Parameters
@@ -53,15 +53,15 @@ export default defineEventHandler((event) => {
```ts
// server/api/posts/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return getPost(id)
})
const id = getRouterParam(event, 'id');
return getPost(id);
});
// Catch-all: server/api/[...path].ts
export default defineEventHandler((event) => {
const path = getRouterParam(event, 'path')
return { path }
})
const path = getRouterParam(event, 'path');
return { path };
});
```
### Query Parameters
@@ -70,10 +70,10 @@ export default defineEventHandler((event) => {
// server/api/search.ts
// GET /api/search?q=nuxt&page=1
export default defineEventHandler((event) => {
const query = getQuery(event)
const query = getQuery(event);
// { q: 'nuxt', page: '1' }
return search(query.q, Number(query.page))
})
return search(query.q, Number(query.page));
});
```
### Request Body
@@ -81,10 +81,10 @@ export default defineEventHandler((event) => {
```ts
// server/api/submit.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const body = await readBody(event);
// Validate and process body
return { success: true, data: body }
})
return { success: true, data: body };
});
```
### Headers and Cookies
@@ -93,24 +93,24 @@ export default defineEventHandler(async (event) => {
// server/api/auth.ts
export default defineEventHandler((event) => {
// Read headers
const auth = getHeader(event, 'authorization')
const auth = getHeader(event, 'authorization');
// Read cookies
const cookies = parseCookies(event)
const token = getCookie(event, 'token')
const cookies = parseCookies(event);
const token = getCookie(event, 'token');
// Set headers
setHeader(event, 'X-Custom-Header', 'value')
setHeader(event, 'X-Custom-Header', 'value');
// Set cookies
setCookie(event, 'token', 'new-token', {
httpOnly: true,
secure: true,
maxAge: 60 * 60 * 24, // 1 day
})
});
return { authenticated: !!token }
})
return { authenticated: !!token };
});
```
## Server Middleware
@@ -120,16 +120,16 @@ Runs on every request before routes:
```ts
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const token = getCookie(event, 'token')
const token = getCookie(event, 'token');
// Attach data to event context
event.context.user = token ? verifyToken(token) : null
})
event.context.user = token ? verifyToken(token) : null;
});
// server/middleware/log.ts
export default defineEventHandler((event) => {
console.log(`${event.method} ${event.path}`)
})
console.log(`${event.method} ${event.path}`);
});
```
Access context in routes:
@@ -137,12 +137,12 @@ Access context in routes:
```ts
// server/api/profile.ts
export default defineEventHandler((event) => {
const user = event.context.user
const user = event.context.user;
if (!user) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
throw createError({ statusCode: 401, message: 'Unauthorized' });
}
return user
})
return user;
});
```
## Error Handling
@@ -150,18 +150,18 @@ export default defineEventHandler((event) => {
```ts
// server/api/users/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
const user = findUser(id)
const id = getRouterParam(event, 'id');
const user = findUser(id);
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'User not found',
})
});
}
return user
})
return user;
});
```
## Server Utils
@@ -171,16 +171,16 @@ Auto-imported in `server/utils/`:
```ts
// server/utils/db.ts
export function useDb() {
return createDbConnection()
return createDbConnection();
}
```
```ts
// server/api/users.ts
export default defineEventHandler(() => {
const db = useDb() // Auto-imported
return db.query('SELECT * FROM users')
})
const db = useDb(); // Auto-imported
return db.query('SELECT * FROM users');
});
```
## Server Plugins
@@ -191,13 +191,13 @@ Run once when server starts:
// server/plugins/db.ts
export default defineNitroPlugin((nitroApp) => {
// Initialize database connection
const db = createDbConnection()
const db = createDbConnection();
// Add to context
nitroApp.hooks.hook('request', (event) => {
event.context.db = db
})
})
event.context.db = db;
});
});
```
## Streaming Responses
@@ -205,22 +205,22 @@ export default defineNitroPlugin((nitroApp) => {
```ts
// server/api/stream.ts
export default defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'text/event-stream')
setHeader(event, 'Cache-Control', 'no-cache')
setHeader(event, 'Connection', 'keep-alive')
setHeader(event, 'Content-Type', 'text/event-stream');
setHeader(event, 'Cache-Control', 'no-cache');
setHeader(event, 'Connection', 'keep-alive');
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(`data: ${JSON.stringify({ count: i })}\n\n`)
await new Promise(r => setTimeout(r, 1000))
controller.enqueue(`data: ${JSON.stringify({ count: i })}\n\n`);
await new Promise((r) => setTimeout(r, 1000));
}
controller.close()
controller.close();
},
})
});
return stream
})
return stream;
});
```
## Server Storage
@@ -230,16 +230,16 @@ Key-value storage with multiple drivers:
```ts
// server/api/cache.ts
export default defineEventHandler(async (event) => {
const storage = useStorage()
const storage = useStorage();
// Set value
await storage.setItem('key', { data: 'value' })
await storage.setItem('key', { data: 'value' });
// Get value
const data = await storage.getItem('key')
const data = await storage.getItem('key');
return data
})
return data;
});
```
Configure storage drivers in `nuxt.config.ts`:
@@ -254,10 +254,10 @@ export default defineNuxtConfig({
},
},
},
})
});
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/server
- https://nuxt.com/docs/directory-structure/server
@@ -14,7 +14,7 @@ SSR-safe replacement for `ref` that shares state across components:
```vue
<script setup lang="ts">
// State is shared by key 'counter' across all components
const counter = useState('counter', () => 0)
const counter = useState('counter', () => 0);
</script>
<template>
@@ -33,19 +33,19 @@ Define reusable state composables:
```ts
// composables/useUser.ts
export function useUser() {
return useState<User | null>('user', () => null)
return useState<User | null>('user', () => null);
}
export function useLocale() {
return useState('locale', () => 'en')
return useState('locale', () => 'en');
}
```
```vue
<script setup lang="ts">
// Same state instance everywhere
const user = useUser()
const locale = useLocale()
const user = useUser();
const locale = useLocale();
</script>
```
@@ -55,11 +55,11 @@ Use `callOnce` to initialize state with async data:
```vue
<script setup lang="ts">
const config = useState('site-config')
const config = useState('site-config');
await callOnce(async () => {
config.value = await $fetch('/api/config')
})
config.value = await $fetch('/api/config');
});
</script>
```
@@ -69,27 +69,27 @@ await callOnce(async () => {
```ts
// ❌ Wrong - causes memory leaks and shared state across requests
export const globalState = ref({ user: null })
export const globalState = ref({ user: null });
```
### ✅ Use useState Instead
```ts
// ✅ Correct - SSR-safe
export const useGlobalState = () => useState('global', () => ({ user: null }))
export const useGlobalState = () => useState('global', () => ({ user: null }));
```
## Clearing State
```ts
// Clear specific state
clearNuxtState('counter')
clearNuxtState('counter');
// Clear multiple states
clearNuxtState(['counter', 'user'])
clearNuxtState(['counter', 'user']);
// Clear all state (use with caution)
clearNuxtState()
clearNuxtState();
```
## With Pinia
@@ -108,38 +108,38 @@ export const useCounterStore = defineStore('counter', {
}),
actions: {
increment() {
this.count++
this.count++;
},
},
})
});
```
```ts
// stores/user.ts (Composition API style)
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const isLoggedIn = computed(() => !!user.value)
const user = ref<User | null>(null);
const isLoggedIn = computed(() => !!user.value);
async function login(credentials: Credentials) {
user.value = await $fetch('/api/login', {
method: 'POST',
body: credentials,
})
});
}
return { user, isLoggedIn, login }
})
return { user, isLoggedIn, login };
});
```
```vue
<script setup lang="ts">
const counterStore = useCounterStore()
const userStore = useUserStore()
const counterStore = useCounterStore();
const userStore = useUserStore();
// Initialize store data once
await callOnce(async () => {
await userStore.fetchUser()
})
await userStore.fetchUser();
});
</script>
```
@@ -148,22 +148,21 @@ await callOnce(async () => {
```ts
// composables/useLocale.ts
export function useLocale() {
return useState('locale', () => useDefaultLocale().value)
return useState('locale', () => useDefaultLocale().value);
}
export function useDefaultLocale(fallback = 'en-US') {
const locale = ref(fallback)
const locale = ref(fallback);
if (import.meta.server) {
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
if (reqLocale) locale.value = reqLocale
}
else if (import.meta.client) {
const navLang = navigator.language
if (navLang) locale.value = navLang
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0];
if (reqLocale) locale.value = reqLocale;
} else if (import.meta.client) {
const navLang = navigator.language;
if (navLang) locale.value = navLang;
}
return locale
return locale;
}
```
@@ -178,15 +177,15 @@ export function useDefaultLocale(fallback = 'en-US') {
```ts
// ❌ Won't work
useState('fn', () => () => console.log('hi'))
useState('instance', () => new MyClass())
useState('fn', () => () => console.log('hi'));
useState('instance', () => new MyClass());
// ✅ Works
useState('data', () => ({ name: 'John', age: 30 }))
useState('items', () => ['a', 'b', 'c'])
useState('data', () => ({ name: 'John', age: 30 }));
useState('items', () => ['a', 'b', 'c']);
```
<!--
<!--
Source references:
- https://nuxt.com/docs/getting-started/state-management
- https://nuxt.com/docs/api/composables/use-state
@@ -15,15 +15,17 @@ Server renders HTML, then hydrates on client:
// nuxt.config.ts - this is the default
export default defineNuxtConfig({
ssr: true,
})
});
```
**Benefits:**
- Fast initial page load (HTML is ready)
- SEO-friendly (content is in HTML)
- Works without JavaScript initially
**How it works:**
1. Server executes Vue code, generates HTML
2. Browser displays HTML immediately
3. JavaScript loads and hydrates the page
@@ -37,15 +39,17 @@ Render entirely in the browser:
// nuxt.config.ts
export default defineNuxtConfig({
ssr: false,
})
});
```
**Benefits:**
- Simpler development (no SSR constraints)
- Cheaper hosting (static files only)
- Works offline
**Use cases:**
- Admin dashboards
- SaaS applications
- Apps behind authentication
@@ -62,24 +66,26 @@ Provide loading UI while app hydrates:
</div>
<style>
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #00dc82;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #00dc82;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
```
@@ -106,21 +112,21 @@ export default defineNuxtConfig({
// Server-rendered (default)
'/api/**': { cors: true },
},
})
});
```
### Route Rules Reference
| Rule | Description |
|------|-------------|
| `prerender: true` | Pre-render at build time |
| `ssr: false` | Client-side only |
| `swr: number \| true` | Stale-while-revalidate caching |
| `isr: number \| true` | Incremental static regeneration |
| `cache: { maxAge: number }` | Cache with TTL |
| `redirect: string` | Redirect to another path |
| `cors: true` | Add CORS headers |
| `headers: object` | Custom response headers |
| Rule | Description |
| --------------------------- | ------------------------------- |
| `prerender: true` | Pre-render at build time |
| `ssr: false` | Client-side only |
| `swr: number \| true` | Stale-while-revalidate caching |
| `isr: number \| true` | Incremental static regeneration |
| `cache: { maxAge: number }` | Cache with TTL |
| `redirect: string` | Redirect to another path |
| `cors: true` | Add CORS headers |
| `headers: object` | Custom response headers |
### Inline Route Rules
@@ -130,7 +136,7 @@ Define per-page:
<script setup lang="ts">
defineRouteRules({
prerender: true,
})
});
</script>
```
@@ -147,7 +153,7 @@ export default defineNuxtConfig({
'/about': { prerender: true },
'/posts/*': { prerender: true },
},
})
});
```
Or use `nuxt generate`:
@@ -164,20 +170,20 @@ export default defineNuxtConfig({
hooks: {
'prerender:routes'({ routes }) {
// Add dynamic routes
const posts = await fetchPostSlugs()
const posts = await fetchPostSlugs();
for (const slug of posts) {
routes.add(`/posts/${slug}`)
routes.add(`/posts/${slug}`);
}
},
},
})
});
```
Or in pages:
```ts
// server/api/posts.ts or a plugin
prerenderRoutes(['/posts/1', '/posts/2', '/posts/3'])
prerenderRoutes(['/posts/1', '/posts/2', '/posts/3']);
```
## Edge-Side Rendering
@@ -190,10 +196,11 @@ export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages', // or 'vercel-edge', 'netlify-edge'
},
})
});
```
Supported platforms:
- Cloudflare Pages/Workers
- Vercel Edge Functions
- Netlify Edge Functions
@@ -206,12 +213,12 @@ Use `import.meta.server` and `import.meta.client`:
<script setup>
if (import.meta.server) {
// Server-only code
console.log('Running on server')
console.log('Running on server');
}
if (import.meta.client) {
// Client-only code
console.log('Running in browser')
console.log('Running in browser');
}
</script>
```
@@ -229,7 +236,7 @@ For components:
</template>
```
<!--
<!--
Source references:
- https://nuxt.com/docs/guide/concepts/rendering
- https://nuxt.com/docs/getting-started/prerendering