Build-Time Data Loading
VitePress provides powerful data loaders that execute at build time, allowing you to load arbitrary data and import it from pages or components. The loaded data is serialized as JSON in the final JavaScript bundle.
Basic Data Loaders
Data loader files must end with .data.js or .data.ts and export a default object with a load() method.
example.data.js
example.data.ts
export default {
load () {
return {
hello: 'world'
}
}
}
Importing Data
Import data from loader files using the data named export:
< script setup >
import { data } from './example.data.js'
</ script >
< template >
< pre > {{ JSON . stringify ( data , null , 2 ) }} </ pre >
</ template >
The loader module is evaluated only in Node.js, so you can import Node APIs and npm dependencies freely.
Loading Local Files
Use the watch option to monitor local files for changes and trigger hot updates:
import fs from 'node:fs'
import { parse } from 'csv-parse/sync'
export default {
watch: [ './data/*.csv' ] ,
load ( watchedFiles ) {
// watchedFiles: array of absolute paths
return watchedFiles . map (( file ) => {
return parse ( fs . readFileSync ( file , 'utf-8' ), {
columns: true ,
skip_empty_lines: true
})
})
}
}
Watch Patterns
The watch option accepts glob patterns :
watch: ['./data/*.csv'] - Watch all CSV files
watch: ['posts/**/*.md'] - Watch markdown files recursively
watch: ['data.json', 'config.yaml'] - Watch specific files
createContentLoader API
For content-focused sites, VitePress provides createContentLoader to simplify loading markdown files:
Create a data loader
import { createContentLoader } from 'vitepress'
export default createContentLoader ( 'posts/*.md' )
Import and use the data
< script setup >
import { data as posts } from './posts.data.js'
</ script >
< template >
< ul >
< li v-for = " post of posts " : key = " post . url " >
< a : href = " post . url " > {{ post . frontmatter . title }} </ a >
</ li >
</ ul >
</ template >
ContentData Interface
The loaded data has the following structure:
interface ContentData {
// Mapped URL for the page (e.g., /posts/hello.html)
url : string
// Frontmatter data of the page
frontmatter : Record < string , any >
// Optional fields (enabled via options)
src ?: string // Raw markdown source
html ?: string // Rendered full page HTML
excerpt ?: string // Rendered excerpt HTML
}
import { createContentLoader } from 'vitepress'
export default createContentLoader ( 'posts/*.md' , {
includeSrc: true , // Include raw markdown
render: true , // Include rendered HTML
excerpt: true , // Include excerpt
transform ( rawData ) {
// Sort by date
return rawData
. sort (( a , b ) => {
return + new Date ( b . frontmatter . date ) - + new Date ( a . frontmatter . date )
})
. map (( page ) => ({
url: page . url ,
title: page . frontmatter . title ,
date: page . frontmatter . date ,
excerpt: page . excerpt
}))
}
})
Be cautious about data size when using includeSrc or render - the data is inlined as JSON in the client bundle.
Using in Build Hooks
Data loaders can be used in build hooks to generate files:
import { createContentLoader } from 'vitepress'
import { writeFileSync } from 'fs'
import { Feed } from 'feed'
export default {
async buildEnd ( siteConfig ) {
const posts = await createContentLoader ( 'posts/*.md' , {
excerpt: true ,
render: true
}). load ()
const feed = new Feed ({
title: 'My Blog' ,
description: 'My blog posts' ,
link: siteConfig . site . base
})
posts . forEach ( post => {
feed . addItem ({
title: post . frontmatter . title ,
link: ` ${ siteConfig . site . base }${ post . url } ` ,
description: post . excerpt ,
content: post . html
})
})
writeFileSync ( '.vitepress/dist/feed.rss' , feed . rss2 ())
}
}
Accessing Configuration
Access VitePress configuration inside loaders:
import type { SiteConfig } from 'vitepress'
const config : SiteConfig = ( globalThis as any ). VITEPRESS_CONFIG
export default {
load () {
console . log ( config . srcDir )
console . log ( config . site . base )
return { /* ... */ }
}
}
Advanced Example: API Index
Generate an API index from markdown files:
import { createContentLoader , defineLoader } from 'vitepress'
import type { ContentData } from 'vitepress'
interface APIEntry {
name : string
url : string
category : string
description : string
}
declare const data : APIEntry []
export { data }
export default defineLoader ({
async load () {
const loader = createContentLoader ( 'api/**/*.md' , {
transform ( raw : ContentData []) : APIEntry [] {
return raw . map (({ url , frontmatter }) => ({
name: frontmatter . title ,
url ,
category: frontmatter . category || 'General' ,
description: frontmatter . description || ''
}))
. sort (( a , b ) => a . name . localeCompare ( b . name ))
}
})
return await loader . load ()
}
})
createContentLoader implements caching based on file modified timestamps to improve dev performance. Cache is automatically invalidated when files change.
File loading uses concurrent processing controlled by buildConcurrency config option (default: CPU cores).
Only include necessary data in transform()
Avoid using render: true unless needed
Filter out large fields from frontmatter
Consider generating static files instead of inlining large datasets