Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
name, description
| name | description |
|---|---|
| vitepress-dynamic-routes | Generate multiple pages from a single markdown template using paths loader files |
Dynamic Routes
Generate many pages from a single markdown file and dynamic data. Useful for blogs, package docs, or any data-driven pages.
Basic Setup
Create a template file with parameter in brackets and a paths loader:
.
└─ packages/
├─ [pkg].md # Route template
└─ [pkg].paths.js # Paths loader
The paths loader exports a paths method returning route parameters:
// packages/[pkg].paths.js
export default {
paths() {
return [
{ params: { pkg: 'foo' }},
{ params: { pkg: 'bar' }},
{ params: { pkg: 'baz' }}
]
}
}
Generated pages:
/packages/foo.html/packages/bar.html/packages/baz.html
Multiple Parameters
.
└─ packages/
├─ [pkg]-[version].md
└─ [pkg]-[version].paths.js
// packages/[pkg]-[version].paths.js
export default {
paths() {
return [
{ params: { pkg: 'foo', version: '1.0.0' }},
{ params: { pkg: 'foo', version: '2.0.0' }},
{ params: { pkg: 'bar', version: '1.0.0' }}
]
}
}
Dynamic Path Generation
From local files:
// packages/[pkg].paths.js
import fs from 'node:fs'
export default {
paths() {
return fs.readdirSync('packages').map(pkg => ({
params: { pkg }
}))
}
}
From remote API:
// packages/[pkg].paths.js
export default {
async paths() {
const packages = await fetch('https://api.example.com/packages').then(r => r.json())
return packages.map(pkg => ({
params: {
pkg: pkg.name,
version: pkg.version
}
}))
}
}
Accessing Params in Page
Template globals:
<!-- packages/[pkg].md -->
# Package: {{ $params.pkg }}
Version: {{ $params.version }}
In script:
<script setup>
import { useData } from 'vitepress'
const { params } = useData()
</script>
<template>
<h1>{{ params.pkg }}</h1>
</template>
Passing Content
For heavy content (raw markdown/HTML from CMS), use content instead of params to avoid bloating the client bundle:
// posts/[slug].paths.js
export default {
async paths() {
const posts = await fetch('https://cms.example.com/posts').then(r => r.json())
return posts.map(post => ({
params: { slug: post.slug },
content: post.content // Raw markdown or HTML
}))
}
}
Render content in template:
<!-- posts/[slug].md -->
---
title: {{ $params.title }}
---
<!-- @content -->
The <!-- @content --> placeholder is replaced with the content from the paths loader.
Watch Option
Auto-rebuild when template or data files change:
// posts/[slug].paths.js
export default {
watch: [
'./templates/**/*.njk',
'../data/**/*.json'
],
paths(watchedFiles) {
const dataFiles = watchedFiles.filter(f => f.endsWith('.json'))
return dataFiles.map(file => {
const data = JSON.parse(fs.readFileSync(file, 'utf-8'))
return {
params: { slug: data.slug },
content: renderTemplate(data)
}
})
}
}
Complete Example: Blog
// posts/[slug].paths.js
import fs from 'node:fs'
import matter from 'gray-matter'
export default {
watch: ['./posts/*.md'],
paths(files) {
return files
.filter(f => !f.includes('[slug]'))
.map(file => {
const content = fs.readFileSync(file, 'utf-8')
const { data, content: body } = matter(content)
const slug = file.match(/([^/]+)\.md$/)[1]
return {
params: {
slug,
title: data.title,
date: data.date
},
content: body
}
})
}
}
<!-- posts/[slug].md -->
---
layout: doc
---
# {{ $params.title }}
<time>{{ $params.date }}</time>
<!-- @content -->
Key Points
- Template file uses
[param]syntax in filename - Paths loader file must be named
[param].paths.jsor.ts paths()returns array of{ params: {...}, content?: string }- Use
$paramsin templates oruseData().paramsin scripts - Use
contentfor heavy data to avoid client bundle bloat watchenables HMR for template/data file changes