Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { FileAfterParseHook } from '@nuxt/content'
import type { Arrayable, AutoI18nConfig, NuxtRobotsRuntimeConfig, RobotsGroupInput, RobotsGroupResolved } from './util'
import fsp from 'node:fs/promises'
import {
Expand Down Expand Up @@ -178,6 +177,10 @@ export interface ModuleOptions {
botDetection?: boolean
}

interface FileAfterParseHook {
content: Record<string, any> & { robots?: unknown, seo?: Record<string, unknown> }
}

export interface ResolvedModuleOptions extends ModuleOptions {
sitemap: string[]
disallow: string[]
Expand Down Expand Up @@ -387,26 +390,22 @@ export default defineNuxtModule<ModuleOptions>({
const contentVersion = await resolveNuxtContentVersion()
const isNuxtContentV3 = contentVersion && contentVersion.version === 3
let isNuxtContentV2 = contentVersion && contentVersion.version === 2
if (isNuxtContentV3) {
if (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) => {
if (typeof ctx.content.robots !== 'undefined') {
let rule = ctx.content.robots
if (typeof rule === 'boolean') {
rule = rule ? config.robotsEnabledValue : config.robotsDisabledValue
}
else if (typeof rule === 'object' && rule !== null) {
rule = robotsDirectivesFromObject(rule).join(', ') || config.robotsEnabledValue
}
// add route rule for the path
;(ctx.content as any).seo = (ctx.content as any).seo || {}
;(ctx.content as any).seo.robots = rule
}
})
}
else if (isNuxtContentV2 && nitroPreset.startsWith('cloudflare')) {
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) => {
if (typeof ctx.content.robots === 'undefined')
return
let rule = ctx.content.robots
if (typeof rule === 'boolean')
rule = rule ? config.robotsEnabledValue : config.robotsDisabledValue
else if (typeof rule === 'object' && rule !== null)
rule = robotsDirectivesFromObject(rule).join(', ') || config.robotsEnabledValue
ctx.content.seo ||= {}
ctx.content.seo.robots = rule
})

if (isNuxtContentV2 && nitroPreset.startsWith('cloudflare')) {
logger.warn('The Nuxt Robots, Nuxt Content integration does not work with CloudFlare yet, the integration will be disabled. Learn more at: https://nuxtseo.com/docs/robots/guides/content')
isNuxtContentV2 = false
}
Expand Down
15 changes: 15 additions & 0 deletions test/e2e/content-hook-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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/content-hook-provider') })

describe('generic content hook provider', () => {
it('applies robots frontmatter through the shared content hook', async () => {
const html = await $fetch('/')

expect(String(html)).toContain('<meta name="robots" content="noindex, nofollow">')
})
})
24 changes: 24 additions & 0 deletions test/fixtures/content-hook-provider/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import NuxtRobots from '../../../src/module'
import { defineNuxtModule } from '@nuxt/kit'

const ContentHookProvider = defineNuxtModule({
meta: { name: 'content-hook-provider' },
setup(_, nuxt) {
nuxt.hook('modules:done', async () => {
const context = {
file: { id: 'pages/index.md' },
collection: { name: 'pages' },
content: { path: '/', robots: false, seo: {} },
}
await nuxt.callHook('content:file:afterParse' as never, context as never)
nuxt.options.runtimeConfig.public.contentHook = context.content
})
},
})

export default defineNuxtConfig({
modules: [NuxtRobots, ContentHookProvider],
runtimeConfig: { public: { contentHook: { seo: {} } } },
site: { url: 'https://nuxtseo.com' },
compatibilityDate: '2026-08-14',
})
10 changes: 10 additions & 0 deletions test/fixtures/content-hook-provider/pages/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { useRuntimeConfig, useSeoMeta } from '#imports'

const content = useRuntimeConfig().public.contentHook as { seo: Record<string, string> }
useSeoMeta(content.seo)
</script>

<template>
<main>Generic content hook provider</main>
</template>
Loading