A production-ready Nuxt starter kit for headless Kirby CMS.
Usage β’ Architecture β’ Deployment
Tip
If internationalization is not a requirement for your project, check out the π§± branch without Nuxt i18n.
If this is your first time building an application with Nuxt and Kirby, take a look at the π Kirby Nuxt Starterkit first to get a basic understanding of this tech stack.
This is a minimal but feature-rich Nuxt starter kit and the evolved version of the Kirby Nuxt Starterkit. It pairs with the π« Cacao Kit backend.
- π Motto: "Everything is a block" β Kirby blocks define what to render for each page
- π£οΈ All pages are rendered by the catch-all route by default (you can still create Nuxt pages)
- π Use Kirby's page structure as the source of truth
- π« Kirby Query Language with
nuxt-kirby - π Internationalization with
@nuxtjs/i18n - π¨ UnoCSS and VueUse pre-wired
- π Global site data similar to Kirby's
$site - π SSR-generated SEO data
- π Prettier & ESLint
- π’ Pre-configured VSCode settings
If you need custom Kirby page blueprints with custom fields, you can create dedicated Nuxt pages and query the content using KQL. See app/pages/about.vue for an example.
- Enable Corepack using
corepack enable - Install dependencies using
pnpm install - Adapt the relevant environment variables:
# Base URL of the Kirby backend
KIRBY_BASE_URL=
# Token for bearer authentication
# See https://kirby.tools/docs/headless/configuration/authentication
KIRBY_API_TOKEN=- Start the development server using
pnpm run dev - Visit localhost:3000
Build the application for production with pnpm run build.
Check out the deployment documentation.
The Cacao Kit follows a clear architectural pattern designed around its block-first approach:
app/
βββ components/
β βββ Kirby/
β βββ Block/ # Individual block components
β βββ Blocks.vue # Block renderer
β βββ Layouts.vue # Layout renderer
βββ composables/
β βββ links.ts # Internal link handling
β βββ page.ts # Page data and SEO meta
β βββ proxy.ts # Development proxy utilities
β βββ site.ts # Site data access
βββ pages/
β βββ [...slug].vue # Universal page renderer
β βββ about.vue # Custom page example
βββ plugins/
β βββ site.ts # Global site data management
βββ queries/ # KQL query definitions
βββ index.ts
βββ about.ts
βββ notes.ts
βββ page.ts
βββ site.ts
βββ prefetch.ts
Every page is rendered through the catch-all route [...slug].vue, which dynamically renders either:
- Layouts: Column-based content using
KirbyLayouts - Blocks: Linear content using
KirbyBlocks
<template>
<div>
<KirbyLayouts v-if="page?.layouts?.length" :layouts="page.layouts" />
<KirbyBlocks v-else-if="page?.blocks" :blocks="page.blocks" />
</div>
</template>- Create the block component in
app/components/Kirby/Block/:
<!-- app/components/Kirby/Block/MyCustomBlock.vue -->
<script setup lang="ts">
import type { KirbyBlock } from '#nuxt-kirby'
defineProps<{
block: KirbyBlock<'my-custom-block'>
}>()
</script>
<template>
<section class="my-custom-block">
<h2>{{ block.content.title }}</h2>
<div v-html="block.content.text" />
</section>
</template>- Register the block in
app/components/Kirby/Blocks.vue:
import { LazyKirbyBlockMyCustomBlock } from '#components'
const blockComponents: Record<string, Component> = {
// Custom blocks
'my-custom-block': LazyKirbyBlockMyCustomBlock,
}Define reusable queries in the queries/ directory:
// app/queries/blog.ts
import type { KirbyQuerySchema } from 'kirby-types'
export const blogQuery: KirbyQuerySchema = {
query: 'page("blog")',
select: {
title: true,
children: {
query: 'page.children.listed',
select: {
title: true,
date: true,
excerpt: 'page.text.excerpt(300)',
cover: {
query: 'page.cover.toFile?.resize(600)',
select: ['url', 'alt'],
},
},
},
},
}Use them in components:
<script setup lang="ts">
import { blogQuery } from '~/queries/blog'
const { locale } = useI18n()
const { data } = await useKql(blogQuery, {
language: locale.value,
})
</script>The kit includes full i18n support with @nuxtjs/i18n.
This kit uses semantic HTML with minimal styling via new.css for demonstration. To implement your own styling, remove the import in app/app.vue and add your custom styles β UnoCSS is already set up.
For maximum performance and CDN compatibility, generate a static site:
pnpm run generateThis creates a fully static version in the dist/ directory that can be hosted on any static hosting service.
Deploy with full SSR capabilities:
pnpm run buildEnsure these environment variables are set in production:
# Required: Your Kirby backend URL
KIRBY_BASE_URL=https://your-kirby-backend.com
# Required: Authentication token for KQL queries
KIRBY_API_TOKEN=your-secret-token
# Optional: Public site URL for SEO and social sharing
NUXT_PUBLIC_SITE_URL=https://your-frontend.com- Production site: cacao-kit.byjohann.dev
What's Kirby?
- getkirby.com β Get to know the CMS.
- Try it β Take a test ride with our online demo. Or download one of our kits to get started.
- Documentation β Read the official guide, reference and cookbook recipes.
- Issues β Report bugs and other problems.
- Feedback β You have an idea for Kirby? Share it.
- Forum β Whenever you get stuck, don't hesitate to reach out for questions and support.
- Discord β Hang out and meet the community.
- YouTube β Watch the latest video tutorials with Bastian.
- Mastodon β Spread the word.
- Instagram β Share your creations: #madewithkirby.
MIT License Β© 2023-PRESENT Johann Schopplich
