Skip to content

Commit 300481a

Browse files
authored
feat: Nuxt Content v3 (#382)
1 parent 2331126 commit 300481a

File tree

17 files changed

+1487
-2816
lines changed

17 files changed

+1487
-2816
lines changed

.github/workflows/test.yml

+6-10
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,23 @@ on:
66
- '**/README.md'
77
- 'docs/**'
88

9+
permissions:
10+
contents: read # access to check out code and install dependencies
11+
912
jobs:
1013
build:
11-
runs-on: ${{ matrix.os }}
12-
13-
strategy:
14-
matrix:
15-
node-version: [22.x]
16-
# os: [ubuntu-latest, windows-latest, macos-latest]
17-
os: [ubuntu-latest]
18-
fail-fast: false
14+
runs-on: ubuntu-latest
1915

2016
steps:
2117
- uses: actions/checkout@v4
2218

2319
- name: Install pnpm
2420
uses: pnpm/action-setup@v4
2521

26-
- name: Use Node.js ${{ matrix.node-version }}
22+
- name: Use Node.js LTS
2723
uses: actions/setup-node@v4
2824
with:
29-
node-version: ${{ matrix.node-version }}
25+
node-version: lts/*
3026
registry-url: https://registry.npmjs.org/
3127
cache: pnpm
3228

build.config.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import { defineBuildConfig } from 'unbuild'
22

33
export default defineBuildConfig({
4+
declaration: true,
5+
rollup: {
6+
emitCJS: true,
7+
},
48
entries: [
59
'src/const.ts',
10+
{ input: 'src/content', name: 'content' },
611
],
712
externals: [
13+
'h3',
14+
'std-env',
15+
'nitropack',
16+
'consola',
17+
'@nuxt/content',
18+
'zod',
819
'ofetch',
920
'consola/utils',
1021
'nuxt',

content.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './dist/content'

docs/content.config.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
SiteConfigModule,
1313
SitemapModule,
1414
} from '../src/const'
15+
import { asSeoCollection } from '../src/content'
1516
import { logger } from './logger'
1617

1718
function getSubModuleCollection(m: NuxtSEOModule) {
@@ -34,34 +35,34 @@ function getSubModuleCollection(m: NuxtSEOModule) {
3435
}
3536
// use github source
3637
logger.info(`🔗 Docs source \`${m.slug}\` using GitHub: ${m.repo}`)
37-
return defineCollection({
38+
return defineCollection(asSeoCollection({
3839
type: 'page',
3940
source: {
4041
repository: `https://github.com/${m.repo}`,
4142
include: 'docs/content/**/*.md',
4243
prefix: `/docs/${m.slug}`,
4344
},
44-
})
45+
}))
4546
}
4647

4748
export default defineContentConfig({
4849
collections: {
49-
nuxtSeo: defineCollection({
50+
nuxtSeo: defineCollection(asSeoCollection({
5051
type: 'page',
5152
source: {
5253
include: '**/*.md',
5354
cwd: resolve('content/nuxtSeo'),
5455
prefix: '/docs/nuxt-seo',
5556
},
56-
}),
57+
})),
5758
robots: getSubModuleCollection(RobotsModule),
5859
sitemap: getSubModuleCollection(SitemapModule),
5960
ogImage: getSubModuleCollection(OgImageModule),
6061
schemaOrg: getSubModuleCollection(SchemaOrgModule),
6162
linkChecker: getSubModuleCollection(LinkCheckerModule),
6263
seoUtils: getSubModuleCollection(SeoUtilsModule),
6364
siteConfig: getSubModuleCollection(SiteConfigModule),
64-
learn: defineCollection({
65+
learn: defineCollection(asSeoCollection({
6566
type: 'page',
6667
source: {
6768
include: '**/*.md',
@@ -76,8 +77,8 @@ export default defineContentConfig({
7677
readTime: z.string(),
7778
ogImageComponent: z.string().optional(),
7879
}),
79-
}),
80-
root: defineCollection({
80+
})),
81+
root: defineCollection(asSeoCollection({
8182
type: 'page',
8283
source: {
8384
include: '**/*.md',
@@ -92,8 +93,8 @@ export default defineContentConfig({
9293
readTime: z.string(),
9394
ogImageComponent: z.string().optional(),
9495
}),
95-
}),
96-
recipes: defineCollection({
96+
})),
97+
recipes: defineCollection(asSeoCollection({
9798
type: 'page',
9899
source: {
99100
include: '**/*.md',
@@ -106,8 +107,7 @@ export default defineContentConfig({
106107
updatedAt: z.string().optional(),
107108
keywords: z.array(z.string()).optional(),
108109
readTime: z.string(),
109-
ogImageComponent: z.string().optional(),
110110
}),
111-
}),
111+
})),
112112
},
113113
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
title: Nuxt Content
3+
description: Integrating Nuxt SEO with Nuxt Content.
4+
---
5+
6+
## Introduction
7+
8+
Most Nuxt SEO modules integrates with Nuxt Content out of the box.
9+
10+
- Nuxt Robots: `robots` ([docs](/docs/robots/guides/content))
11+
- Nuxt Sitemap: `sitemap` ([docs](/docs/sitemap/guides/content))
12+
- Nuxt OG Image: `ogImage` ([docs](/docs/og-image/integrations/content))
13+
- Nuxt Schema.org: `schemaOrg` ([docs](/docs/schema-org/guides/content))
14+
- Nuxt Link Checker: Uses content APIs to check links
15+
16+
For Nuxt Content v3 you would need to configure the modules to work with Nuxt Content individually, however, Nuxt SEO
17+
provides a way to configure all modules at once.
18+
19+
For Nuxt Content v2, please see the individual module documentation for how to configure them.
20+
21+
## Setup Nuxt Content v3
22+
23+
In Nuxt Content v3 we need to use the `asSeoCollection()`{lang="ts"} function to augment any collections
24+
to be able to use the SEO modules.
25+
26+
```ts [content.config.ts]
27+
import { defineCollection, defineContentConfig } from '@nuxt/content'
28+
import { asSeoCollection } from '@nuxtjs/seo/content'
29+
30+
export default defineContentConfig({
31+
collections: {
32+
content: defineCollection(
33+
asSeoCollection({
34+
type: 'page',
35+
source: '**/*.md',
36+
}),
37+
),
38+
},
39+
})
40+
```
41+
42+
To ensure the tags actually gets rendered you need to ensure you're using the SEO composable.
43+
44+
```vue [[...slug].vue]
45+
<script setup lang="ts">
46+
import { queryCollection, useRoute } from '#imports'
47+
48+
const route = useRoute()
49+
const { data: page } = await useAsyncData(`page-${route.path}`, () => {
50+
return queryCollection('content').path(route.path).first()
51+
})
52+
if (page.value?.ogImage) {
53+
defineOgImage(page.value?.ogImage) // <-- Nuxt OG Image
54+
}
55+
// Ensure the schema.org is rendered
56+
useHead(page.value.head || {}) // <-- Nuxt Schema.org
57+
useSeoMeta(page.value.seo || {}) // <-- Nuxt Robots
58+
</script>
59+
```
60+
61+
Due to current Nuxt Content v3 limitations, you must load the Nuxt SEO module before the content module.
62+
63+
```ts
64+
export default defineNuxtConfig({
65+
modules: [
66+
'@nuxtjs/seo',
67+
'@nuxt/content' // <-- Must be after @nuxtjs/seo
68+
]
69+
})
70+
```
71+
72+
## Usage
73+
74+
For the full options available for each module, please see the individual module documentation.
75+
76+
```md
77+
---
78+
ogImage:
79+
component: HelloWorld
80+
props:
81+
title: "Hello World"
82+
description: "This is a description"
83+
image: "/hello-world.png"
84+
sitemap:
85+
lastmod: 2025-01-01
86+
robots: index, nofollow
87+
schemaOrg:
88+
- "@type": "BlogPosting"
89+
headline: "How to Use Our Product"
90+
author:
91+
type: "Person"
92+
name: "Jane Smith"
93+
datePublished: "2023-10-01"
94+
---
95+
96+
# Hello World
97+
```

docs/nuxt.config.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,18 @@ export default defineNuxtConfig({
1616
'@vueuse/nuxt',
1717
'@nuxthub/core',
1818
'@nuxt/fonts',
19-
'@nuxt/content',
2019
'@nuxt/scripts',
2120
'@nuxt/image',
2221
// maybe buggy
2322
'nuxt-rebundle',
2423
'nuxt-build-cache',
2524
NuxtSEO,
25+
'@nuxt/content',
2626
async (_, nuxt) => {
2727
nuxt.hooks.hook('nitro:init', (nitro) => {
2828
// from sponsorkit
2929
nitro.options.alias.sharp = 'unenv/runtime/mock/empty'
3030
nitro.options.alias.pnpapi = 'unenv/runtime/mock/empty' // ?
31-
nitro.options.alias['#content/server'] = resolve('./server/content-v2')
3231
nitro.hooks.hook('compiled', async (_nitro) => {
3332
const routesPath = resolve(nitro.options.output.publicDir, '_routes.json')
3433
if (existsSync(routesPath)) {
@@ -66,12 +65,7 @@ export default defineNuxtConfig({
6665
},
6766
},
6867

69-
robots: {
70-
disableNuxtContentIntegration: true,
71-
},
72-
7368
sitemap: {
74-
strictNuxtContentPaths: true,
7569
xslColumns: [
7670
{ label: 'URL', width: '100%' },
7771
],
@@ -258,6 +252,12 @@ export default defineNuxtConfig({
258252
},
259253
},
260254

255+
linkChecker: {
256+
report: {
257+
markdown: true,
258+
},
259+
},
260+
261261
routeRules: {
262262
// for doc linking purposes
263263
'/robots': { redirect: { to: '/docs/robots/getting-started/installation', statusCode: 301 } },
@@ -345,7 +345,6 @@ export default defineNuxtConfig({
345345

346346
ogImage: {
347347
zeroRuntime: true,
348-
strictNuxtContentPaths: true,
349348
fonts: [
350349
'Hubot+Sans:400',
351350
'Hubot+Sans:700',
@@ -364,26 +363,27 @@ export default defineNuxtConfig({
364363
provider: 'iconify',
365364
},
366365

366+
seo: {
367+
meta: {
368+
themeColor: [
369+
{ content: '#18181b', media: '(prefers-color-scheme: dark)' },
370+
{ content: 'white', media: '(prefers-color-scheme: light)' },
371+
],
372+
},
373+
},
374+
367375
app: {
368376
pageTransition: {
369377
name: 'page',
370378
mode: 'out-in',
371379
},
372380
head: {
373-
seoMeta: {
374-
themeColor: [
375-
{ content: '#18181b', media: '(prefers-color-scheme: dark)' },
376-
{ content: 'white', media: '(prefers-color-scheme: light)' },
377-
],
378-
},
379381
templateParams: {
380382
separator: '·',
381383
},
382-
383384
bodyAttrs: {
384385
class: 'antialiased font-sans text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-900',
385386
},
386-
387387
},
388388
},
389389

docs/package.json

+10-10
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,41 @@
1717
"@iconify-json/noto": "^1.2.2",
1818
"@iconify-json/ph": "^1.2.2",
1919
"@iconify-json/radix-icons": "^1.2.2",
20-
"@iconify-json/simple-icons": "^1.2.19",
20+
"@iconify-json/simple-icons": "^1.2.20",
2121
"@iconify-json/uil": "^1.2.3",
2222
"@iconify-json/unjs": "^1.2.0",
2323
"@iconify-json/vscode-icons": "^1.2.10",
2424
"@inspira-ui/plugins": "^0.0.1",
25-
"@nuxt/content": "3.0.0-alpha.8",
26-
"@nuxt/devtools": "1.6.0",
25+
"@nuxt/content": "^3.0.0",
26+
"@nuxt/devtools": "2.0.0-beta.3",
2727
"@nuxt/fonts": "^0.10.3",
2828
"@nuxt/image": "^1.9.0",
2929
"@nuxt/scripts": "^0.9.5",
3030
"@nuxt/ui": "3.0.0-alpha.6",
3131
"@nuxt/ui-pro": "2.0.0-alpha.6",
32-
"@nuxthub/core": "0.8.6",
33-
"@nuxtjs/mdc": "^0.12.1",
34-
"@unovis/vue": "1.4.4",
32+
"@nuxthub/core": "^0.8.12",
33+
"@nuxtjs/mdc": "^0.13.1",
34+
"@unovis/vue": "^1.5.0",
3535
"@vueuse/core": "^12.4.0",
3636
"@vueuse/motion": "^2.2.6",
3737
"@vueuse/nuxt": "^12.4.0",
3838
"case-police": "^0.7.2",
3939
"clsx": "^2.1.1",
4040
"consola": "^3.4.0",
4141
"markdownlint-cli": "^0.43.0",
42-
"nuxt": "^3.15.1",
42+
"nuxt": "^3.15.2",
4343
"nuxt-build-cache": "^0.1.1",
4444
"nuxt-content-twoslash": "^0.1.2",
4545
"nuxt-lodash": "^2.5.3",
4646
"nuxt-rebundle": "^0.0.2",
4747
"octokit": "^4.1.0",
4848
"ofetch": "^1.4.1",
4949
"radix-vue": "^1.9.12",
50-
"shiki": "^1.26.2",
50+
"shiki": "^2.0.0",
5151
"shiki-transformer-color-highlight": "^0.2.0",
52-
"sponsorkit": "^0.16.2",
52+
"sponsorkit": "^16.3.0",
5353
"tailwind-merge": "^2.6.0",
5454
"tailwindcss-animate": "^1.0.7",
55-
"wrangler": "^3.101.0"
55+
"wrangler": "^3.103.2"
5656
}
5757
}

docs/server/content-v2.ts

-7
This file was deleted.

0 commit comments

Comments
 (0)