Skip to content

Compatibility ​

nuxt-toc is a Nuxt module — it runs inside your Nuxt app at build time and adds the <TableOfContents> component. That means its compatibility is tied to your Nuxt and Content versions.

Supported versions ​

PackageSupportedPrimary target
nuxt^3.16.0 || ^4.0.0Nuxt 4
@nuxt/content^2.0.0 || ^3.0.0both
vue3.x—
node>=20.0.0—

The published package declares @nuxt/kit >=3.16 <5 as a dependency so Nuxt 3.16+ and Nuxt 4 hosts can both resolve a matching kit. Your package.json should still pin nuxt itself to ^3.16 or ^4.

One Content major per app

@nuxt/content v2 and v3 cannot be installed in the same node_modules tree — they export conflicting APIs. This repo keeps separate playgrounds (playgrounds/content-v2 and playgrounds/content-v3) each with its own package.json. Your app should install one major: npm install @nuxt/content@^2 or npm install @nuxt/content@^3.

How dual Content support works ​

On setup() (src/module.ts:171), the module:

  1. Detects the major by walking up from nuxt.options.rootDir looking for node_modules/@nuxt/content/package.json and reading its version (src/utils/detect-content-major.ts:32). Walking covers hoisted installs (e.g. playgrounds that depend on a parent node_modules). Unsupported majors return null.
  2. Publishes runtime config at runtimeConfig.public.nuxtToc.contentMajor (2, 3, or null) so the client can be inspected in Vue devtools.
  3. Registers exactly one plugin — never both (they import different queries and would break the tree):
Detected majorPlugin registeredQuery used
2src/runtime/plugins/fetch-v2.tsqueryContent(path).findOne()
3src/runtime/plugins/fetch-v3.tsqueryCollection(collection).path(path).first()
nullnone — logs a warningPass-in :toc still works

The fetch plugins are added with addPlugin() and provide $nuxtTocFetch via defineNuxtPlugin().provide. The component (src/runtime/components/TableOfContents.vue:295) calls that function only when :toc is omitted — otherwise it reuses normalizeToc(toc).

Consequence you may notice: if you build with Content v3 and open the app with Content v2 installed (or vice versa), the auto-fetch plugin will not match and the TOC will stay in the “Could not load” empty state. The fix is to align the installed package with what you build against, then run npx nuxi prepare.

Module vs playground Nuxt versions ​

  • This package (nuxt-toc@3.0.0): built against Nuxt 4 tooling. CI runs nuxt-module-build, vitest, and both playground builds.
  • Playgrounds: both are Nuxt 4 apps, differing only in the Content major. Ports: content-v3 on 3000, content-v2 on 3001.

You can still use the published module from a Nuxt 3.16+ host — just keep @nuxt/kit aligned with your Nuxt version (your lockfile will do this after npm install).

Content v2 vs v3 differences that affect the TOC ​

Topicv2v3
Installnpm install @nuxt/content@^2npm install @nuxt/content@^3
Config filenone (put .md in content/)content.config.ts with defineCollection({ type: 'page', source: '**/*.md' })
Query in pagesqueryContent(path).findOne()queryCollection('content').path(path).first()
Collection conceptno collections — collection prop is ignoredrequired — nuxtToc.collection / collection prop must match a content.config.ts key
ContentRendererContentRenderer or legacy ContentDocContentRenderer
TOC locationpage.body.toc.linkspage.body.toc.links — same shape on both majors
TOC depth capset via content.build.markdown.toc (if exposed)defineCollection / content.build.markdown.toc: { depth, searchDepth }

See setup guides: Content v3 and Content v2.

Quick answers ​

Which Content version should a new app pick? Content v3 is recommended for new Nuxt 4 apps. The module works equally well with either — the choice is about Content features, not TOC behavior.

Can I migrate an app from Content v2 to v3 later? Yes — see Migrating your app: Content v2 → v3. The TOC usage (:toc="page.body.toc") does not change; only the query helper does.

Do I need to configure anything for dual support? No. Install the Content major you want, list ['nuxt-toc', '@nuxt/content'] in modules, and the module picks the right plugin. Only Content v3 needs content.config.ts.

How do I debug which major was detected? Open Vue devtools → runtimeConfig.public.nuxtToc.contentMajor should be 2 or 3. In dev, the module also warns if Content is installed but not registered in modules (“pass :toc and skip auto-fetch”).

Next step ​

Released under the MIT License.