v2.12.0

Content

Bitrix24 UI integrates with Nuxt Content to deliver beautiful typography and consistent component styling.

Installation

To get started, you can follow the official guide or in summary:

pnpm add @nuxt/content

Then, add the @nuxt/content module in your nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@bitrix24/b24ui-nuxt',
    '@nuxt/content'
  ],
  css: ['~/assets/css/main.css']
})
You need to register @nuxt/content after @bitrix24/b24ui-nuxt in the modules array, otherwise the prose components will not be available.

Configuration

When using Tailwind CSS classes in your markdown content files, you need to ensure Tailwind can detect and generate the necessary utility classes. By default, Tailwind's automatic content detection might not pick up classes written in markdown files.

To fix this, use the @source directive in your CSS file to explicitly include your content directory:

app/assets/css/main.css
@import "tailwindcss";
@import "@bitrix24/b24ui-nuxt";

@source "../../../content/**/*";

This ensures that:

  • Tailwind scans all markdown files in your content directory
  • Any utility classes used in your markdown are included in the final CSS
  • Dynamic classes in MDC components or custom Vue components within your content work properly
You can also use glob patterns to be more specific about which files to scan:
  • @source "../../../content/docs/**/*.md" - Only scan markdown in the docs folder
  • @source "../../../content/**/*.{md,yml}" - Include both markdown and YAML files
Learn more about Tailwind's automatic content detection and best practices for optimizing build performance.

Client-only apps (SPA)

Prose components work in a client-only app (ssr: false) exactly as they do with server rendering — nothing about markdown rendering is disabled in this mode. Nuxt Content moves its database into the browser instead: on the first query the client downloads a dump from /__nuxt_content/<collection>/sql_dump.txt and answers every query locally through WASM SQLite.

That dump is the whole collection, not just the pages you link to, and it is cached in localStorage. Anything you put in a collection that a client-rendered page queries is readable by every visitor, so keep drafts and non-public documents in a separate collection.

The build still needs SQLite

The dump is produced at build time, so the build needs a working SQLite adapter even though the deployed app never runs one. This is where a client-only build usually breaks: the default adapter, better-sqlite3, is a native module that has to be compiled during install. Without it the build fails with Could not locate the bindings file and produces no dump, so no content reaches the page.

On Node.js 22.5 or later you can skip the native binding entirely and use Node's built-in node:sqlite. One line, no compiler, nothing package-manager specific:

nuxt.config.ts
export default defineNuxtConfig({
  content: {
    experimental: {
      sqliteConnector: 'native'
    }
  }
})

This is what this documentation site is built with.

On older Node.js better-sqlite3 has to compile, and pnpm does not run a dependency's install scripts unless you say so:

pnpm-workspace.yaml
allowBuilds:
  better-sqlite3: true

Running pnpm approve-builds writes that entry for you, which is the version-proof way to do it — pnpm 11 removed the older onlyBuiltDependencies and ignoredBuiltDependencies settings in favour of this map. It also fails the install outright with ERR_PNPM_IGNORED_BUILDS; pnpm 10 lets the install succeed and only prints a note, so that is the version where this is easy to miss. Approve the one package you need rather than reaching for pnpm approve-builds --all or dangerouslyAllowAllBuilds: an install script runs arbitrary code on your machine and in CI, which is what that default protects you from.

npm and yarn run install scripts by default, so a better-sqlite3 failure there is a missing C++ toolchain rather than a blocked script — install one, or switch the connector as above.

See the Nuxt Content configuration reference for the full list of SQLite connectors.

When a page renders nothing

Work outwards from the build — each step has a different answer:

  1. Is the dump in the output? Look for .output/public/__nuxt_content/<collection>/sql_dump.txt after a build. If it is missing, the build never produced it, and the SQLite adapter above is where to look.
  2. Does the browser get it? The Network tab should show that file being fetched on the first query. A 404 there usually means the site is served from a sub-path that app.baseURL does not account for.
  3. Does the query match anything? That fetch throws rather than returning nothing, so a missing dump surfaces in error, not in data. An empty data with no error is a different problem, usually a path filter that matches no document.
<script setup lang="ts">
// `content` is the default collection name — use yours if you renamed it
const { data: all, error } = await useAsyncData('all', () => queryCollection('content').all())

// `content/index.md` has the path `/`, not `/index`
if (import.meta.dev) {
  console.log(error.value, all.value?.length, all.value?.map(item => item.path))
}
</script>
Collections marked private: true are deliberately left out of the browser dump and go through a server route instead, so they need a running server — a fully static export cannot answer them.

Components

You might be using @nuxt/content to build a documentation. To help you with that, we've built some components that you can use in your pages:

  • a built-in full-text search command palette with ContentSearch, replacing the need for Algolia DocSearch
  • a sticky Table of Contents with the ContentToc component
  • a prev / next navigation with the ContentSurround component

Typography

Bitrix24 UI provides its own custom implementations of all prose components for seamless integration with @nuxt/content. This approach ensures consistent styling, complete control over typography, and perfect alignment with the Bitrix24 UI design system so your content always looks and feels cohesive out of the box.

Discover the full Typography system and explore all available prose components for rich, consistent content presentation.

Utils

Bitrix24 UI ships helpers to reshape the data returned by @nuxt/content into the format its components expect.

mapContentNavigation

This util will map the navigation from queryCollectionNavigation and transform it recursively into an array of objects that can be used by various components.

mapContentNavigation(navigation, options?)

  • navigation: The navigation tree (array of ContentNavigationItem).
  • options (optional):
    • labelAttribute: (string) Which field to use as label (title by default)
    • deep: (number or undefined) Controls how many levels of navigation are included (undefined by default, includes all levels)

Example: As shown in the breadcrumb example below, it's commonly used to transform the navigation data into the correct format.

app.vue
<script setup lang="ts">
import { mapContentNavigation } from '@bitrix24/b24ui-nuxt/utils/content'
import { findPageBreadcrumb } from '@nuxt/content/utils'

const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('content'))

const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value?.path, { indexAsChild: true })).map(({ icon, ...link }) => link), { deep: 0 })
</script>

<template>
  <B24Page>
    <B24PageHeader v-bind="page">
      <template #headline>
        <B24Breadcrumb :items="breadcrumb" />
      </template>
    </B24PageHeader>
  </B24Page>
</template>