fix: convert apps/website from submodule to regular directory
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: nuxt-seo
|
||||
description: Nuxt SEO meta-module with robots, sitemap, og-image, schema-org. Use when configuring SEO, generating sitemaps, creating OG images, or adding structured data.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Nuxt SEO
|
||||
|
||||
```bash
|
||||
npx nuxi module add @nuxtjs/seo
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
Working with:
|
||||
|
||||
- SEO configuration (site URL, name, indexability)
|
||||
- Robots.txt and sitemap.xml generation
|
||||
- Dynamic OG image generation
|
||||
- JSON-LD structured data (schema.org)
|
||||
- Breadcrumbs and canonical URLs
|
||||
|
||||
## Loading Files
|
||||
|
||||
**Consider loading these reference files based on your task:**
|
||||
|
||||
- [ ] [references/site-config.md](references/site-config.md) - if configuring site URL, name, or SEO foundation
|
||||
- [ ] [references/crawlability.md](references/crawlability.md) - if setting up robots.txt or sitemap.xml
|
||||
- [ ] [references/og-image.md](references/og-image.md) - if generating dynamic OG images
|
||||
- [ ] [references/schema-org.md](references/schema-org.md) - if adding JSON-LD structured data
|
||||
- [ ] [references/utilities.md](references/utilities.md) - if working with breadcrumbs, canonical URLs, or link checking
|
||||
|
||||
**DO NOT load all files at once.** Load only what's relevant to your current task.
|
||||
|
||||
## Site Config
|
||||
|
||||
Foundation for all SEO modules. Configure `site` in `nuxt.config.ts`, access via `useSiteConfig()`. See [references/site-config.md](references/site-config.md) for full options.
|
||||
|
||||
## Module Overview
|
||||
|
||||
| Module | Purpose | Key API |
|
||||
| ----------------- | --------------- | ----------------------------- |
|
||||
| nuxt-site-config | Shared config | `useSiteConfig()` |
|
||||
| @nuxtjs/robots | robots.txt | `useRobotsRule()` |
|
||||
| @nuxtjs/sitemap | sitemap.xml | `defineSitemapEventHandler()` |
|
||||
| nuxt-og-image | OG images | `defineOgImage()` |
|
||||
| nuxt-schema-org | JSON-LD | `useSchemaOrg()` |
|
||||
| nuxt-seo-utils | Meta utilities | `useBreadcrumbItems()` |
|
||||
| nuxt-link-checker | Link validation | Build-time checks |
|
||||
|
||||
## Nuxt Content v3
|
||||
|
||||
Use `asSeoCollection()` for automatic sitemap, og-image, and schema-org from frontmatter:
|
||||
|
||||
```ts
|
||||
// content.config.ts
|
||||
import { defineCollection, defineContentConfig } from '@nuxt/content'
|
||||
import { asSeoCollection } from '@nuxtjs/seo/content'
|
||||
|
||||
export default defineContentConfig({
|
||||
collections: {
|
||||
posts: defineCollection(asSeoCollection({ type: 'page', source: 'posts/**' }))
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Important:** Load `@nuxtjs/seo` before `@nuxt/content` in modules array:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxtjs/seo', '@nuxt/content']
|
||||
})
|
||||
```
|
||||
|
||||
Frontmatter fields: `ogImage`, `sitemap`, `robots`, `schemaOrg`.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [nuxt-content](../nuxt-content/SKILL.md) - For MDC rendering with SEO frontmatter
|
||||
|
||||
## Links
|
||||
|
||||
- [Documentation](https://nuxtseo.com)
|
||||
- [GitHub](https://github.com/harlan-zw/nuxt-seo)
|
||||
|
||||
## Token Efficiency
|
||||
|
||||
Main skill: ~250 tokens. Each sub-file: ~400-600 tokens. Only load files relevant to current task.
|
||||
@@ -0,0 +1,153 @@
|
||||
# Crawlability: Robots & Sitemap
|
||||
|
||||
## Robots.txt
|
||||
|
||||
Auto-generated at `/robots.txt`. Respects `site.indexable` setting.
|
||||
|
||||
### Configuration
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
robots: {
|
||||
// Block AI crawlers
|
||||
blockAiBots: true,
|
||||
// Block non-SEO bots (reduces server load)
|
||||
blockNonSeoBots: true,
|
||||
// Custom rules
|
||||
groups: [
|
||||
{ userAgent: '*', disallow: ['/admin'] }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Per-Page Control
|
||||
|
||||
```ts
|
||||
// Disable indexing
|
||||
useRobotsRule('noindex, nofollow')
|
||||
|
||||
// Object syntax with AI directives
|
||||
useRobotsRule({
|
||||
noindex: true,
|
||||
nofollow: true,
|
||||
noai: true, // Block AI training
|
||||
noimageai: true, // Block AI image training
|
||||
'max-snippet': 150, // Preview controls
|
||||
'max-image-preview': 'large'
|
||||
})
|
||||
```
|
||||
|
||||
Route rules:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/admin/**': { robots: 'noindex, nofollow' },
|
||||
'/hidden': { robots: false }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Nuxt Content Frontmatter
|
||||
|
||||
```yaml
|
||||
---
|
||||
robots: noindex, nofollow
|
||||
# Or structured:
|
||||
robots:
|
||||
noindex: true
|
||||
nofollow: true
|
||||
---
|
||||
```
|
||||
|
||||
## Sitemap.xml
|
||||
|
||||
Auto-generated at `/sitemap.xml` from app routes.
|
||||
|
||||
### Configuration
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
sitemap: {
|
||||
sources: ['/api/__sitemap__/urls'],
|
||||
exclude: ['/admin/**', '/secret'],
|
||||
// For static sites - no runtime generation
|
||||
zeroRuntime: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Dynamic URLs via API
|
||||
|
||||
```ts
|
||||
// server/api/__sitemap__/urls.ts
|
||||
import { defineSitemapEventHandler } from '#imports'
|
||||
import type { SitemapUrlInput } from '#sitemap/types'
|
||||
|
||||
export default defineSitemapEventHandler(async () => {
|
||||
const posts = await $fetch('/api/posts')
|
||||
return posts.map(post => ({
|
||||
loc: post.path,
|
||||
lastmod: post.updatedAt,
|
||||
// Image sitemap
|
||||
images: [{ loc: post.image, title: post.title }],
|
||||
// Video sitemap
|
||||
videos: [{ content_loc: post.videoUrl, title: post.title }]
|
||||
} satisfies SitemapUrlInput))
|
||||
})
|
||||
```
|
||||
|
||||
### Per-Page Control
|
||||
|
||||
Route rules:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/blog/**': { sitemap: { changefreq: 'daily', priority: 0.9 } },
|
||||
'/hidden': { sitemap: false }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Nuxt Content frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
sitemap:
|
||||
changefreq: weekly
|
||||
priority: 0.8
|
||||
lastmod: 2025-01-15
|
||||
---
|
||||
```
|
||||
|
||||
### Multiple Sitemaps
|
||||
|
||||
For large sites:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
sitemap: {
|
||||
sitemaps: {
|
||||
pages: { include: ['/**'], exclude: ['/blog/**'] },
|
||||
blog: { include: ['/blog/**'] }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Generates `/pages-sitemap.xml`, `/blog-sitemap.xml`, and `/sitemap_index.xml`.
|
||||
|
||||
### i18n Sitemaps
|
||||
|
||||
With `@nuxtjs/i18n`, auto-generates per-locale sitemaps with `hreflang` alternates.
|
||||
|
||||
## Debug
|
||||
|
||||
In development:
|
||||
|
||||
- Robots: Check `/robots.txt` directly
|
||||
- Sitemap: Visit `/__sitemap__/debug.json` for raw data
|
||||
@@ -0,0 +1,170 @@
|
||||
# OG Image Generation
|
||||
|
||||
Dynamic Open Graph image generation using Vue components.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
// Component-first (recommended)
|
||||
defineOgImage('NuxtSeo', { title: 'My Page Title' })
|
||||
|
||||
// Object syntax
|
||||
defineOgImage({ component: 'NuxtSeo', title: 'My Page Title' })
|
||||
|
||||
// Disable OG image
|
||||
defineOgImage(false)
|
||||
```
|
||||
|
||||
## Built-in Template
|
||||
|
||||
The `NuxtSeo` template supports:
|
||||
|
||||
```ts
|
||||
defineOgImage('NuxtSeo', {
|
||||
title: 'Hello World',
|
||||
description: 'My description',
|
||||
theme: '#3b82f6',
|
||||
colorMode: 'dark',
|
||||
icon: 'carbon:cloud',
|
||||
siteName: 'My Site',
|
||||
siteLogo: '/logo.png'
|
||||
})
|
||||
```
|
||||
|
||||
## Multiple Images Per Page
|
||||
|
||||
Use `key` for platform-specific images:
|
||||
|
||||
```ts
|
||||
// Default OG image (1200x600)
|
||||
defineOgImage('NuxtSeo', { title: 'Default' })
|
||||
|
||||
// Square for WhatsApp (800x800)
|
||||
defineOgImage('NuxtSeo', {
|
||||
title: 'Square',
|
||||
key: 'square',
|
||||
width: 800,
|
||||
height: 800
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Vue Components
|
||||
|
||||
Create in `components/OgImage/`:
|
||||
|
||||
```vue
|
||||
<!-- components/OgImage/Blog.vue -->
|
||||
<script setup lang="ts">
|
||||
defineProps<{ title: string; author: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col justify-center items-center bg-gradient-to-br from-blue-500 to-purple-600 p-12">
|
||||
<h1 class="text-6xl font-bold text-white text-center">{{ title }}</h1>
|
||||
<p class="text-2xl text-white/80 mt-4">By {{ author }}</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Use in pages:
|
||||
|
||||
```ts
|
||||
defineOgImage('OgImageBlog', { title: 'My Post', author: 'John' })
|
||||
```
|
||||
|
||||
## Renderers
|
||||
|
||||
| Renderer | Speed | CSS Support | Edge | Best For |
|
||||
| -------- | ----- | ----------- | ---- | -------------------------- |
|
||||
| satori | Fast | Partial | ✅ | Default, most templates |
|
||||
| chromium | Slow | Full | ❌ | Complex designs, prerender |
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
ogImage: {
|
||||
defaults: { renderer: 'satori' }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Satori Limitations
|
||||
|
||||
- No `display: grid` - use `flex`
|
||||
- No `position: absolute` without explicit dimensions
|
||||
- Fonts: use `@nuxt/fonts` with `global: true` for best results
|
||||
|
||||
## Configuration
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
ogImage: {
|
||||
defaults: {
|
||||
component: 'NuxtSeo',
|
||||
width: 1200,
|
||||
height: 600,
|
||||
cacheMaxAgeSeconds: 60 * 60 * 24 * 3 // 3 days
|
||||
},
|
||||
// For static sites
|
||||
zeroRuntime: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Nuxt Content
|
||||
|
||||
Frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
ogImage:
|
||||
component: OgImageBlog
|
||||
props:
|
||||
author: John Doe
|
||||
---
|
||||
```
|
||||
|
||||
With `asSeoCollection()` (see main SKILL.md):
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const { data: page } = await useAsyncData(() => queryCollection('posts').path(route.path).first())
|
||||
if (page.value?.ogImage)
|
||||
defineOgImage(page.value.ogImage)
|
||||
</script>
|
||||
```
|
||||
|
||||
## Debug
|
||||
|
||||
- Preview: `/__og-image__/image/[path]/og.png`
|
||||
- Inspector: Enable `ogImage: { debug: true }` in config
|
||||
|
||||
## Screenshots
|
||||
|
||||
Capture page as OG image (requires Chromium):
|
||||
|
||||
```ts
|
||||
defineOgImageScreenshot({
|
||||
colorScheme: 'dark',
|
||||
mask: '.navigation, .footer',
|
||||
selector: '.article-content'
|
||||
})
|
||||
```
|
||||
|
||||
## Route Rules
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/blog/**': { ogImage: { component: 'OgImageBlog' } },
|
||||
'/admin/**': { ogImage: false }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Community templates are dev-only. Before deploying, eject:
|
||||
|
||||
```bash
|
||||
npx nuxt-og-image eject NuxtSeo
|
||||
```
|
||||
@@ -0,0 +1,182 @@
|
||||
# Schema.org Structured Data
|
||||
|
||||
JSON-LD structured data for rich search results.
|
||||
|
||||
## Site Identity
|
||||
|
||||
Configure once in `nuxt.config.ts`:
|
||||
|
||||
```ts
|
||||
import { defineOrganization } from 'nuxt-schema-org/schema'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
schemaOrg: {
|
||||
identity: defineOrganization({
|
||||
name: 'My Company',
|
||||
url: 'https://example.com',
|
||||
logo: '/logo.png',
|
||||
sameAs: ['https://twitter.com/mycompany', 'https://github.com/mycompany']
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For personal sites:
|
||||
|
||||
```ts
|
||||
import { definePerson } from 'nuxt-schema-org/schema'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
schemaOrg: {
|
||||
identity: definePerson({
|
||||
name: 'John Doe',
|
||||
url: 'https://johndoe.com',
|
||||
image: '/avatar.jpg',
|
||||
sameAs: ['https://twitter.com/johndoe']
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Page-Level Schema
|
||||
|
||||
Define functions are **auto-imported** in components (no import needed):
|
||||
|
||||
```ts
|
||||
// Article page
|
||||
useSchemaOrg([
|
||||
defineArticle({
|
||||
headline: 'My Article Title',
|
||||
description: 'Article description',
|
||||
image: '/article-image.jpg',
|
||||
datePublished: '2025-01-15',
|
||||
dateModified: '2025-01-20',
|
||||
author: { name: 'John Doe', url: 'https://johndoe.com' }
|
||||
})
|
||||
])
|
||||
```
|
||||
|
||||
```ts
|
||||
// Product page (include url in offers for Google validation)
|
||||
useSchemaOrg([
|
||||
defineProduct({
|
||||
name: 'Product Name',
|
||||
description: 'Product description',
|
||||
image: '/product.jpg',
|
||||
offers: {
|
||||
price: 99.99,
|
||||
priceCurrency: 'USD',
|
||||
availability: 'InStock',
|
||||
url: 'https://example.com/product'
|
||||
}
|
||||
})
|
||||
])
|
||||
```
|
||||
|
||||
## Define Functions
|
||||
|
||||
| Function | Use Case |
|
||||
| ----------------------- | ---------------------- |
|
||||
| `defineArticle()` | Blog posts, news |
|
||||
| `defineProduct()` | E-commerce products |
|
||||
| `defineFAQPage()` | FAQ pages |
|
||||
| `defineHowTo()` | Tutorial/guide pages |
|
||||
| `defineRecipe()` | Recipe pages |
|
||||
| `defineEvent()` | Events |
|
||||
| `defineLocalBusiness()` | Business info |
|
||||
| `defineVideo()` | Video content |
|
||||
| `defineBreadcrumb()` | Breadcrumb navigation |
|
||||
| `defineWebPage()` | Generic page |
|
||||
| `defineWebSite()` | Site-wide (auto-added) |
|
||||
| `defineJobPosting()` | Job listings |
|
||||
| `defineSoftwareApp()` | Software/apps |
|
||||
| `defineService()` | Services |
|
||||
|
||||
## Data Inference
|
||||
|
||||
Module auto-infers from page head:
|
||||
|
||||
- `title` → WebPage name
|
||||
- `description` → WebPage description
|
||||
- `og:image` → WebPage image
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
Auto-generated from route path, or customize:
|
||||
|
||||
```ts
|
||||
useSchemaOrg([
|
||||
defineBreadcrumb({
|
||||
itemListElement: [
|
||||
{ name: 'Home', item: '/' },
|
||||
{ name: 'Blog', item: '/blog' },
|
||||
{ name: 'My Post', item: '/blog/my-post' }
|
||||
]
|
||||
})
|
||||
])
|
||||
```
|
||||
|
||||
Or use the `useBreadcrumbItems()` composable (from seo-utils):
|
||||
|
||||
```ts
|
||||
const items = useBreadcrumbItems()
|
||||
useSchemaOrg([defineBreadcrumb({ itemListElement: items })])
|
||||
```
|
||||
|
||||
## FAQ Page
|
||||
|
||||
```ts
|
||||
useSchemaOrg([
|
||||
defineFAQPage({
|
||||
mainEntity: [
|
||||
{ name: 'What is your return policy?', acceptedAnswer: 'You can return within 30 days.' },
|
||||
{ name: 'How do I contact support?', acceptedAnswer: 'Email us at support@example.com' }
|
||||
]
|
||||
})
|
||||
])
|
||||
```
|
||||
|
||||
## Nuxt Content
|
||||
|
||||
Frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: My Article
|
||||
schemaOrg:
|
||||
- type: BlogPosting
|
||||
headline: My Article
|
||||
datePublished: 2025-01-15
|
||||
author:
|
||||
type: Person
|
||||
name: John Doe
|
||||
---
|
||||
```
|
||||
|
||||
With `asSeoCollection()` (see main SKILL.md), ensure schema renders:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const { data: page } = await useAsyncData(() => queryCollection('posts').path(route.path).first())
|
||||
useHead(page.value?.head || {})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Debug & Validation
|
||||
|
||||
- Debug endpoint: `/__schema-org__/debug.json` in dev
|
||||
- Config: `schemaOrg: { debug: true }`
|
||||
- [Google Rich Results Test](https://search.google.com/test/rich-results)
|
||||
- [Schema.org Validator](https://validator.schema.org/)
|
||||
|
||||
## Route Rules
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/blog/**': {
|
||||
schemaOrg: { type: 'Article' }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,101 @@
|
||||
# Site Config
|
||||
|
||||
Foundation module providing shared configuration for all SEO modules.
|
||||
|
||||
## Configuration
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
site: {
|
||||
url: 'https://example.com', // Required for absolute URLs
|
||||
name: 'My Site', // Site name (used in titles, schema)
|
||||
description: 'Site description', // Default meta description
|
||||
defaultLocale: 'en', // Default language
|
||||
indexable: true, // Allow search engine indexing
|
||||
trailingSlash: false, // URL trailing slash preference
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Environment-Based Indexing
|
||||
|
||||
Control indexing per environment using `NUXT_SITE_*` env vars:
|
||||
|
||||
```bash
|
||||
# .env.production
|
||||
NUXT_SITE_URL=https://example.com
|
||||
NUXT_SITE_ENV=production
|
||||
|
||||
# .env.staging
|
||||
NUXT_SITE_URL=https://staging.example.com
|
||||
NUXT_SITE_ENV=staging
|
||||
```
|
||||
|
||||
The module auto-detects `env` and sets `indexable: false` for non-production environments.
|
||||
|
||||
For explicit control:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
site: {
|
||||
url: process.env.NUXT_SITE_URL,
|
||||
// Explicit: only index when explicitly set to 'true'
|
||||
indexable: process.env.NUXT_SITE_INDEXABLE === 'true'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Note:** `!== 'false'` defaults to `true` when env var is undefined - use `=== 'true'` for fail-safe behavior.
|
||||
|
||||
## Runtime Access
|
||||
|
||||
```ts
|
||||
const site = useSiteConfig()
|
||||
console.log(site.url, site.name, site.description)
|
||||
```
|
||||
|
||||
Works in components, composables, and server routes.
|
||||
|
||||
## i18n Integration
|
||||
|
||||
Automatically integrates with `@nuxtjs/i18n`:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
site: {
|
||||
url: 'https://example.com',
|
||||
defaultLocale: 'en',
|
||||
},
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'en', language: 'en-US' },
|
||||
{ code: 'fr', language: 'fr-FR' },
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Locale-specific overrides in `site` object:
|
||||
|
||||
```ts
|
||||
site: {
|
||||
name: 'My Site',
|
||||
locales: {
|
||||
fr: { name: 'Mon Site' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Override Per-Page
|
||||
|
||||
Use route rules for page-specific config:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/admin/**': { site: { indexable: false } },
|
||||
'/fr/**': { site: { name: 'Mon Site', defaultLocale: 'fr' } }
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,203 @@
|
||||
# SEO Utilities
|
||||
|
||||
Additional utilities from nuxt-seo-utils and nuxt-link-checker.
|
||||
|
||||
## Canonical URLs
|
||||
|
||||
Automatic canonical URLs based on site config.
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
seoUtils: {
|
||||
canonicalQueryWhitelist: ['page', 'sort'], // Keep these query params
|
||||
redirectToCanonicalSiteUrl: true // 301 to canonical domain
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Override per-page:
|
||||
|
||||
```ts
|
||||
useHead({
|
||||
link: [{ rel: 'canonical', href: 'https://example.com/preferred-url' }]
|
||||
})
|
||||
```
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
Generate breadcrumb items from current route:
|
||||
|
||||
```ts
|
||||
const items = useBreadcrumbItems()
|
||||
// [{ label: 'Home', to: '/' }, { label: 'Blog', to: '/blog' }, { label: 'My Post' }]
|
||||
```
|
||||
|
||||
For schema.org integration, see [schema-org.md](schema-org.md#breadcrumbs).
|
||||
|
||||
Render in template:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<nav aria-label="Breadcrumb">
|
||||
<ol class="flex gap-2">
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<NuxtLink v-if="item.to" :to="item.to">{{ item.label }}</NuxtLink>
|
||||
<span v-else>{{ item.label }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
```
|
||||
|
||||
Customize labels in route meta:
|
||||
|
||||
```ts
|
||||
// pages/blog/[slug].vue
|
||||
definePageMeta({
|
||||
breadcrumb: { label: 'Article' }
|
||||
})
|
||||
```
|
||||
|
||||
## Title Templates
|
||||
|
||||
Set site-wide title template:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
titleTemplate: '%s | My Site'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Override per-page:
|
||||
|
||||
```ts
|
||||
useHead({
|
||||
title: 'Page Title',
|
||||
titleTemplate: '%s - Different Template'
|
||||
})
|
||||
```
|
||||
|
||||
## Meta Defaults
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
meta: [
|
||||
{ name: 'author', content: 'My Name' },
|
||||
{ property: 'og:site_name', content: 'My Site' }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Link Checker
|
||||
|
||||
Build-time validation of links.
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
linkChecker: {
|
||||
failOnError: true, // Default: fail build on errors
|
||||
exclude: ['/api/**'],
|
||||
skipInspections: ['missing-hash'],
|
||||
report: { html: true } // Generate HTML report
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Inspections:**
|
||||
|
||||
- `no-error-response` - 404/500 errors
|
||||
- `no-baseless` - Missing base URL
|
||||
- `no-javascript` - javascript: links
|
||||
- `trailing-slash` - Inconsistent slashes
|
||||
- `missing-hash` - Invalid anchor targets
|
||||
- `no-uppercase-chars` - URL casing
|
||||
- `absolute-site-urls` - Hardcoded domain
|
||||
|
||||
### Ignoring Links
|
||||
|
||||
```html
|
||||
<a href="/maybe-broken" data-link-checker-ignore>Link</a>
|
||||
```
|
||||
|
||||
## File-Based Icons
|
||||
|
||||
Place favicon files in `public/`:
|
||||
|
||||
```
|
||||
public/
|
||||
├── favicon.ico
|
||||
├── favicon.svg # Modern browsers
|
||||
├── apple-touch-icon.png
|
||||
└── site.webmanifest
|
||||
```
|
||||
|
||||
Auto-detected and added to `<head>`.
|
||||
|
||||
For SVG favicon with dark mode support:
|
||||
|
||||
```svg
|
||||
<!-- public/favicon.svg -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #fff; }
|
||||
}
|
||||
</style>
|
||||
<path d="..."/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
## Social Meta Tags
|
||||
|
||||
Automatic Open Graph and Twitter cards from site config (see [site-config.md](site-config.md)).
|
||||
|
||||
Override per-page:
|
||||
|
||||
```ts
|
||||
useSeoMeta({
|
||||
title: 'Page Title',
|
||||
description: 'Page description',
|
||||
ogImage: '/images/page-og.png',
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
```
|
||||
|
||||
## Trailing Slash Redirect
|
||||
|
||||
Enforce consistent URLs:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
site: {
|
||||
trailingSlash: false // Redirect /blog/ to /blog
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Debug Panel
|
||||
|
||||
Enable comprehensive debug panel:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
seo: { debug: true }
|
||||
})
|
||||
```
|
||||
|
||||
Shows in dev:
|
||||
|
||||
- Current meta tags
|
||||
- Schema.org data
|
||||
- OG image preview
|
||||
- Sitemap/robots status
|
||||
Reference in New Issue
Block a user