diff --git a/src/module.ts b/src/module.ts index 41cc35b2..0b01c170 100644 --- a/src/module.ts +++ b/src/module.ts @@ -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 { @@ -178,6 +177,10 @@ export interface ModuleOptions { botDetection?: boolean } +interface FileAfterParseHook { + content: Record & { robots?: unknown, seo?: Record } +} + export interface ResolvedModuleOptions extends ModuleOptions { sitemap: string[] disallow: string[] @@ -387,26 +390,22 @@ export default defineNuxtModule({ 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 } diff --git a/test/e2e/content-hook-provider.test.ts b/test/e2e/content-hook-provider.test.ts new file mode 100644 index 00000000..e6cc8ff1 --- /dev/null +++ b/test/e2e/content-hook-provider.test.ts @@ -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('') + }) +}) diff --git a/test/fixtures/content-hook-provider/nuxt.config.ts b/test/fixtures/content-hook-provider/nuxt.config.ts new file mode 100644 index 00000000..e9143492 --- /dev/null +++ b/test/fixtures/content-hook-provider/nuxt.config.ts @@ -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', +}) diff --git a/test/fixtures/content-hook-provider/pages/index.vue b/test/fixtures/content-hook-provider/pages/index.vue new file mode 100644 index 00000000..9f7e85a5 --- /dev/null +++ b/test/fixtures/content-hook-provider/pages/index.vue @@ -0,0 +1,10 @@ + + +