We replaced Directus with Nuxt Content (@nuxt/content) — a git-based CMS that uses Markdown files stored in content/. The site at veripath.co.uk is a Nuxt 3 app with all content pulled from these files instead of a database.
| Component | What it does |
|---|---|
@nuxt/content |
Content module — parses Markdown/JSON/YAML files and serves them via queryCollection() |
nuxt-studio |
Visual editor (TipTap WYSIWYG) for editing content directly on the site |
| Forgejo | Self-hosted Git server at git.veripath.co.uk (v14.0.5, Gitea-compatible) |
All content lives under /root/work/veripath-site/content/ (volume-mounted into the Docker container):
content/
pages/ # CMS pages (e.g. about-us.md, privacy.md)
blog/ # Blog posts
policies/ # Policies (some public, some staff-only)
business-plan/ # Business plan sections (staff-only)
Each file uses standard Markdown with YAML frontmatter:
---
title: "2. Data Security Policy"
slug: data-security-policy
effective_date: "2026-05-12"
review_date: "2027-05-31"
owner: "admin"
status: "published"
visibility: "internal"
---
# Content here...
nano /root/work/veripath-site/content/policies/data-security-policy.md
Clone from git.veripath.co.uk, edit locally, push:
git clone ssh://git@git.veripath.co.uk:2222/matthew/veripath-site.git
cd veripath-site
# edit files in content/
git add content/
git commit -m "Update policy"
git push
After editing (via Studio or CLI), deploy to production:
/opt/deploy-veripath-site.sh
This rebuilds the Docker image, removes the old container, and starts a new one. The content/ directory is volume-mounted, so edits persist across deployments.
Pages use the v3 API (queryCollection, not the deprecated queryContent):
// List all policies
const { data: policies } = await useAsyncData('policies', () => {
return queryCollection('content').where('path', 'LIKE', '/policies/%').all()
})
// Single policy by slug
const { data: policy } = await useAsyncData('policy-' + slug, () => {
return queryCollection('content').path('/policies/' + slug).first()
})
Key differences from v2:
queryCollection('content') not queryContent().all() not .find().first() not .findOne().path('/path/to/doc') not .where({ path: ... })meta.* (e.g. policy.meta.slug, not policy.slug)| Path | Purpose |
|---|---|
/root/work/veripath-site/ |
Nuxt app root |
nuxt.config.ts |
Studio & Nuxt Content config |
content/ |
All Markdown content files |
pages/ |
Vue page components using queryCollection |
/opt/deploy-veripath-site.sh |
Deployment script |
veripath-sitegp_booking_app_gp_booking_network (172.18.0.2)forgejo)