diff --git a/docs/content/3.advanced/2.content.md b/docs/content/3.advanced/2.content.md
index 99ba9fbf..60de3ace 100644
--- a/docs/content/3.advanced/2.content.md
+++ b/docs/content/3.advanced/2.content.md
@@ -60,6 +60,34 @@ export default defineNuxtConfig({
})
```
+## Setup comark-content
+
+[comark-content](https://github.com/harlan-zw/harlan-nuxt/tree/main/packages/comark-content) is a Markdown-only content module. Nuxt Robots supports it first-party.
+
+No schema opt in is needed. Write the `robots` key in frontmatter and the module maps it onto the page's `seo.robots`.
+
+```md [content/foo.md]
+---
+robots: false
+---
+
+# Foo
+```
+
+Render it with `useSeoMeta()`{lang="ts"} in your catch-all page, the same as with Nuxt Content v3.
+
+```vue [pages/[...slug\].vue]
+
+```
+
+See [Usage](#usage) for the values the `robots` key accepts.
+
## Setup Nuxt Content v2
In Nuxt Content v2 markdown files require either [Document Driven Mode](https://content.nuxt.com/document-driven/introduction) or a `path` key to be set
diff --git a/package.json b/package.json
index b73a702c..cc3ac9bf 100644
--- a/package.json
+++ b/package.json
@@ -83,6 +83,7 @@
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@arethetypeswrong/cli": "catalog:dev",
+ "@harlan-zw/comark-content": "catalog:dev",
"@headlessui/vue": "catalog:dev",
"@iconify-json/logos": "catalog:dev",
"@iconify-json/ri": "catalog:dev",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 70d7e602..2713f733 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -124,6 +124,9 @@ catalogs:
'@arethetypeswrong/cli':
specifier: ^0.18.5
version: 0.18.5
+ '@harlan-zw/comark-content':
+ specifier: ^0.1.3
+ version: 0.1.3
'@headlessui/vue':
specifier: ^1.7.23
version: 1.7.23
@@ -202,6 +205,9 @@ importers:
'@arethetypeswrong/cli':
specifier: catalog:dev
version: 0.18.5
+ '@harlan-zw/comark-content':
+ specifier: catalog:dev
+ version: 0.1.3(magic-string@1.1.0)(magicast@0.5.4)(nuxt@4.5.2(c208e49f62ede97a435f751ca2ecda2c))(oxc-parser@0.131.0)(rolldown@1.2.1)(shiki@4.4.1)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.4)(vite@8.2.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.2(postcss@8.5.25))(csso@5.0.5)(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)))(vue@3.5.41(typescript@6.0.3))
'@headlessui/vue':
specifier: catalog:dev
version: 1.7.23(vue@3.5.41(typescript@6.0.3))
@@ -1280,6 +1286,13 @@ packages:
engines: {node: '>=6'}
hasBin: true
+ '@harlan-zw/comark-content@0.1.3':
+ resolution: {integrity: sha512-nmGSQjp8lqLHFenlKXg/7mB8p82Gw0spp8Jr3aSvaHpyxQwqS3enDE+IxrnouH15bCjRQeZab+v7uWDWPWpNGA==}
+ engines: {node: '>=22.12.0'}
+ peerDependencies:
+ nuxt: '>=4.5.0 <5.0.0'
+ vue: '>=3.5.0 <4.0.0'
+
'@headlessui/vue@1.7.23':
resolution: {integrity: sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==}
engines: {node: '>=10'}
@@ -3292,9 +3305,15 @@ packages:
'@types/katex@0.16.8':
resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==}
+ '@types/linkify-it@5.0.0':
+ resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+ '@types/mdurl@2.0.0':
+ resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
@@ -4318,6 +4337,23 @@ packages:
colortranslator@5.0.0:
resolution: {integrity: sha512-Z3UPUKasUVDFCDYAjP2fmlVRf1jFHJv1izAmPjiOa0OCIw1W7iC8PZ2GsoDa8uZv+mKyWopxxStT9q05+27h7w==}
+ comark@0.6.2:
+ resolution: {integrity: sha512-hlWa7KlGPfRxovQavnHZlLlTUKrfwE7/o4ewCY0FzqFtRvVT8VdslrumQwELIiK9utsurpoSWCBFj1yVMtDOtw==}
+ peerDependencies:
+ beautiful-mermaid: ^1.1.3
+ katex: ^0.17.0
+ rangi: ^2.2.0
+ shiki: ^4.3.1
+ peerDependenciesMeta:
+ beautiful-mermaid:
+ optional: true
+ katex:
+ optional: true
+ rangi:
+ optional: true
+ shiki:
+ optional: true
+
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@@ -4657,16 +4693,32 @@ packages:
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+ dom-serializer@3.1.1:
+ resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==}
+ engines: {node: '>=20.19.0'}
+
domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+ domelementtype@3.0.0:
+ resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==}
+ engines: {node: '>=20.19.0'}
+
domhandler@5.0.3:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
+ domhandler@6.0.1:
+ resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==}
+ engines: {node: '>=20.19.0'}
+
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+ domutils@4.0.2:
+ resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==}
+ engines: {node: '>=20.19.0'}
+
dot-prop@10.2.0:
resolution: {integrity: sha512-BTJ9aZYL3vCfZlZOBLy9v8TUqWGQ0pzFnygKwFZt5udj6viBoFIBviKPUoZLDCPn1FoXffv6McQFDenrm5Krfw==}
engines: {node: '>=20'}
@@ -5624,6 +5676,10 @@ packages:
html-whitespace-sensitive-tag-names@3.0.1:
resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==}
+ htmlparser2@12.0.0:
+ resolution: {integrity: sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==}
+ engines: {node: '>=20.19.0'}
+
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
@@ -5881,6 +5937,10 @@ packages:
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
hasBin: true
+ js-yaml@5.3.0:
+ resolution: {integrity: sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==}
+ hasBin: true
+
jsdoc-type-pratt-parser@7.3.0:
resolution: {integrity: sha512-DoyJXo7x/n48M3NsGOs9QnEws0ft0tV3YsSgvWMNxz2hZtz+Q6fpqUD96lVYX4a+jcEkzHeFNDJOn68TzXfbdA==}
engines: {node: '>=20.0.0'}
@@ -6135,6 +6195,9 @@ packages:
limiter@1.1.5:
resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==}
+ linkify-it@5.0.2:
+ resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==}
+
linkifyjs@4.3.3:
resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==}
@@ -6237,6 +6300,9 @@ packages:
resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==}
engines: {node: '>=18'}
+ markdown-exit@1.1.0-beta.2:
+ resolution: {integrity: sha512-8CzMGVlFZ4DEfnc8KU+4ycUW2SIOuiXqCHD7z51ecVEi/weyc0f2ylQbCm4KoKuVlTZSuMUMnWT0hTyquZ7anQ==}
+
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
@@ -6311,6 +6377,9 @@ packages:
mdn-data@2.29.0:
resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==}
+ mdurl@2.1.0:
+ resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==}
+
media-typer@1.1.1:
resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==}
engines: {node: '>= 0.8'}
@@ -7425,6 +7494,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -7453,6 +7526,10 @@ packages:
resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==}
engines: {node: '>= 0.6'}
+ rangi@2.2.0:
+ resolution: {integrity: sha512-zMXLNs+3ejB2dg5kjJdNbllNq6FrdtEbHA9f3POxbSIHb0CyslALbY8qSDCE2r+/6Ku7xaVHKtnC5qHbF2SgNg==}
+ hasBin: true
+
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
@@ -8176,6 +8253,9 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
ufo@1.6.4:
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
@@ -9926,6 +10006,23 @@ snapshots:
yargs: 17.7.3
optional: true
+ '@harlan-zw/comark-content@0.1.3(magic-string@1.1.0)(magicast@0.5.4)(nuxt@4.5.2(c208e49f62ede97a435f751ca2ecda2c))(oxc-parser@0.131.0)(rolldown@1.2.1)(shiki@4.4.1)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.4)(vite@8.2.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.2(postcss@8.5.25))(csso@5.0.5)(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)))(vue@3.5.41(typescript@6.0.3))':
+ dependencies:
+ '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.131.0)(rolldown@1.2.1)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.4)(vite@8.2.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.2(postcss@8.5.25))(csso@5.0.5)(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)))
+ comark: 0.6.2(rangi@2.2.0)(shiki@4.4.1)
+ nuxt: 4.5.2(c208e49f62ede97a435f751ca2ecda2c)
+ rangi: 2.2.0
+ vue: 3.5.41(typescript@6.0.3)
+ transitivePeerDependencies:
+ - beautiful-mermaid
+ - katex
+ - magic-string
+ - magicast
+ - oxc-parser
+ - rolldown
+ - shiki
+ - unplugin
+
'@headlessui/vue@1.7.23(vue@3.5.41(typescript@6.0.3))':
dependencies:
'@tanstack/vue-virtual': 3.13.35(vue@3.5.41(typescript@6.0.3))
@@ -12406,10 +12503,14 @@ snapshots:
'@types/katex@0.16.8': {}
+ '@types/linkify-it@5.0.0': {}
+
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
+ '@types/mdurl@2.0.0': {}
+
'@types/ms@2.1.0': {}
'@types/node@26.1.2':
@@ -13875,6 +13976,16 @@ snapshots:
colortranslator@5.0.0: {}
+ comark@0.6.2(rangi@2.2.0)(shiki@4.4.1):
+ dependencies:
+ entities: 8.0.0
+ htmlparser2: 12.0.0
+ js-yaml: 5.3.0
+ markdown-exit: 1.1.0-beta.2
+ optionalDependencies:
+ rangi: 2.2.0
+ shiki: 4.4.1
+
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
@@ -14211,18 +14322,36 @@ snapshots:
domhandler: 5.0.3
entities: 4.5.0
+ dom-serializer@3.1.1:
+ dependencies:
+ domelementtype: 3.0.0
+ domhandler: 6.0.1
+ entities: 8.0.0
+
domelementtype@2.3.0: {}
+ domelementtype@3.0.0: {}
+
domhandler@5.0.3:
dependencies:
domelementtype: 2.3.0
+ domhandler@6.0.1:
+ dependencies:
+ domelementtype: 3.0.0
+
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
domelementtype: 2.3.0
domhandler: 5.0.3
+ domutils@4.0.2:
+ dependencies:
+ dom-serializer: 3.1.1
+ domelementtype: 3.0.0
+ domhandler: 6.0.1
+
dot-prop@10.2.0:
dependencies:
type-fest: 5.8.0
@@ -15555,6 +15684,13 @@ snapshots:
html-whitespace-sensitive-tag-names@3.0.1: {}
+ htmlparser2@12.0.0:
+ dependencies:
+ domelementtype: 3.0.0
+ domhandler: 6.0.1
+ domutils: 4.0.2
+ entities: 8.0.0
+
http-errors@2.0.1:
dependencies:
depd: 2.0.0
@@ -15803,6 +15939,10 @@ snapshots:
dependencies:
argparse: 2.0.1
+ js-yaml@5.3.0:
+ dependencies:
+ argparse: 2.0.1
+
jsdoc-type-pratt-parser@7.3.0: {}
jsdoc-type-pratt-parser@8.0.0: {}
@@ -16016,6 +16156,10 @@ snapshots:
limiter@1.1.5: {}
+ linkify-it@5.0.2:
+ dependencies:
+ uc.micro: 2.1.0
+
linkifyjs@4.3.3: {}
listhen@1.10.1(srvx@0.11.22):
@@ -16144,6 +16288,16 @@ snapshots:
type-fest: 4.41.0
web-worker: 1.5.0
+ markdown-exit@1.1.0-beta.2:
+ dependencies:
+ '@types/linkify-it': 5.0.0
+ '@types/mdurl': 2.0.0
+ entities: 7.0.1
+ linkify-it: 5.0.2
+ mdurl: 2.1.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
markdown-table@3.0.4: {}
marked-terminal@7.3.0(marked@9.1.6):
@@ -16306,6 +16460,8 @@ snapshots:
mdn-data@2.29.0: {}
+ mdurl@2.1.0: {}
+
media-typer@1.1.1: {}
merge-descriptors@2.0.0: {}
@@ -17997,6 +18153,8 @@ snapshots:
picocolors: 1.1.1
sade: 1.8.1
+ punycode.js@2.3.1: {}
+
punycode@2.3.1: {}
qs@6.15.3:
@@ -18016,6 +18174,8 @@ snapshots:
range-parser@1.3.0: {}
+ rangi@2.2.0: {}
+
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
@@ -18910,6 +19070,8 @@ snapshots:
typescript@6.0.3: {}
+ uc.micro@2.1.0: {}
+
ufo@1.6.4: {}
ultrahtml@1.7.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index fe5fb4db..669ec03e 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -22,6 +22,7 @@ minimumReleaseAgeExclude:
- '@nuxt/schema@4.5.0'
- '@nuxt/vite-builder@4.5.0'
- nuxt@4.5.0
+ - '@harlan-zw/comark-content@0.1.3'
shellEmulator: true
trustPolicy: no-downgrade
trustPolicyExclude:
@@ -78,6 +79,7 @@ catalog:
catalogs:
dev:
'@arethetypeswrong/cli': ^0.18.5
+ '@harlan-zw/comark-content': ^0.1.3
'@headlessui/vue': ^1.7.23
'@iconify-json/logos': ^1.2.12
'@iconify-json/ri': ^1.2.10
diff --git a/src/kit.ts b/src/kit.ts
index 6cd53998..3b5dc8d4 100644
--- a/src/kit.ts
+++ b/src/kit.ts
@@ -1 +1,2 @@
-export { isNuxtGenerate, resolveNitroPreset, resolveNuxtContentVersion } from 'nuxtseo-shared/kit'
+export type { ContentProvider } from 'nuxtseo-shared/kit'
+export { isNuxtGenerate, resolveContentProvider, resolveNitroPreset } from 'nuxtseo-shared/kit'
diff --git a/src/module.ts b/src/module.ts
index 41cc35b2..296af252 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -22,7 +22,7 @@ import { withoutTrailingSlash, withTrailingSlash } from 'ufo'
import { AiBots, NonHelpfulBots } from './const'
import { setupDevToolsUI } from './devtools'
import { mapPathForI18nPages, resolveI18nConfig, splitPathForI18nLocales } from './i18n'
-import { isNuxtGenerate, resolveNitroPreset, resolveNuxtContentVersion } from './kit'
+import { isNuxtGenerate, resolveContentProvider, resolveNitroPreset } from './kit'
import { registerTypeTemplates } from './templates'
import {
asArray,
@@ -215,6 +215,10 @@ export default defineNuxtModule({
version: '>=2',
optional: true,
},
+ '@harlan-zw/comark-content': {
+ version: '>=0.1.2',
+ optional: true,
+ },
},
defaults: {
enabled: true,
@@ -384,11 +388,15 @@ export default defineNuxtModule({
}
const nitroPreset = resolveNitroPreset(nuxt.options.nitro)
- const contentVersion = await resolveNuxtContentVersion()
- const isNuxtContentV3 = contentVersion && contentVersion.version === 3
- let isNuxtContentV2 = contentVersion && contentVersion.version === 2
- if (isNuxtContentV3) {
- if (hasNuxtModule('Content', nuxt)) {
+ const contentProvider = await resolveContentProvider(nuxt)
+ const isNuxtContentV3 = contentProvider._tag === 'NuxtContent' && contentProvider.version === 3
+ let isNuxtContentV2 = contentProvider._tag === 'NuxtContent' && contentProvider.version === 2
+ const isComarkContent = contentProvider._tag === 'Comark'
+ // comark-content fires the same build hook with the same context shape, so the
+ // frontmatter mapping is identical. It reads Markdown into Nitro server assets
+ // rather than a database, so unlike Nuxt Content v2 it needs no Cloudflare opt out.
+ if (isNuxtContentV3 || isComarkContent) {
+ if (isNuxtContentV3 && hasNuxtModule('Content', nuxt)) {
logger.warn('You have loaded `@nuxt/content` before `@nuxtjs/robots`, this may cause issues with the integration. Please ensure `@nuxtjs/robots` is loaded first.')
}
nuxt.hooks.hook('content:file:afterParse' as any, (ctx: FileAfterParseHook) => {
diff --git a/test/e2e/comark-content.test.ts b/test/e2e/comark-content.test.ts
new file mode 100644
index 00000000..57e3f12d
--- /dev/null
+++ b/test/e2e/comark-content.test.ts
@@ -0,0 +1,29 @@
+import { createResolver } from '@nuxt/kit'
+import { $fetch, setup } from '@nuxt/test-utils'
+import { describe, expect, it } from 'vitest'
+
+const { resolve } = createResolver(import.meta.url)
+
+await setup({
+ rootDir: resolve('../fixtures/comark-content'),
+})
+
+describe('comark-content', () => {
+ it('maps the robots frontmatter onto the page seo meta', async () => {
+ const barHtml = await $fetch('/bar')
+ expect(String(barHtml).match(//)).toMatchInlineSnapshot(`
+ [
+ "",
+ "test",
+ ]
+ `)
+
+ const fooHtml = await $fetch('/foo')
+ expect(String(fooHtml).match(//)).toMatchInlineSnapshot(`
+ [
+ "",
+ "noindex, nofollow",
+ ]
+ `)
+ }, 60000)
+})
diff --git a/test/fixtures/comark-content/.nuxtrc b/test/fixtures/comark-content/.nuxtrc
new file mode 100644
index 00000000..167e6c78
--- /dev/null
+++ b/test/fixtures/comark-content/.nuxtrc
@@ -0,0 +1,2 @@
+imports.autoImport=true
+typescript.includeWorkspace=true
diff --git a/test/fixtures/comark-content/app.vue b/test/fixtures/comark-content/app.vue
new file mode 100644
index 00000000..8f62b8bf
--- /dev/null
+++ b/test/fixtures/comark-content/app.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/test/fixtures/comark-content/content.config.ts b/test/fixtures/comark-content/content.config.ts
new file mode 100644
index 00000000..ce214828
--- /dev/null
+++ b/test/fixtures/comark-content/content.config.ts
@@ -0,0 +1,16 @@
+import { dirname, resolve } from 'node:path'
+import { defineCollection, defineContentConfig } from '@harlan-zw/comark-content'
+
+const dirName = dirname(import.meta.url.replace('file://', ''))
+
+export default defineContentConfig({
+ collections: {
+ content: defineCollection({
+ type: 'page',
+ source: {
+ include: '**/*.md',
+ cwd: resolve(dirName, 'content'),
+ },
+ }),
+ },
+})
diff --git a/test/fixtures/comark-content/content/bar.md b/test/fixtures/comark-content/content/bar.md
new file mode 100644
index 00000000..2f082a81
--- /dev/null
+++ b/test/fixtures/comark-content/content/bar.md
@@ -0,0 +1,6 @@
+---
+path: /bar
+robots: "test"
+---
+
+# bar
diff --git a/test/fixtures/comark-content/content/foo.md b/test/fixtures/comark-content/content/foo.md
new file mode 100644
index 00000000..91f2dc51
--- /dev/null
+++ b/test/fixtures/comark-content/content/foo.md
@@ -0,0 +1,7 @@
+---
+path: '/foo'
+robots: false
+date: 2026-10-10
+---
+
+# foo
diff --git a/test/fixtures/comark-content/nuxt.config.ts b/test/fixtures/comark-content/nuxt.config.ts
new file mode 100644
index 00000000..647f1479
--- /dev/null
+++ b/test/fixtures/comark-content/nuxt.config.ts
@@ -0,0 +1,14 @@
+import NuxtRobots from '../../../src/module'
+
+export default defineNuxtConfig({
+ modules: [
+ NuxtRobots,
+ '@harlan-zw/comark-content',
+ ],
+
+ site: {
+ url: 'https://nuxtseo.com',
+ },
+
+ compatibilityDate: '2024-12-06',
+})
diff --git a/test/fixtures/comark-content/pages/[...slug].vue b/test/fixtures/comark-content/pages/[...slug].vue
new file mode 100644
index 00000000..ae860280
--- /dev/null
+++ b/test/fixtures/comark-content/pages/[...slug].vue
@@ -0,0 +1,18 @@
+
+
+
+
+