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 - π Global site data similar to Kirby's
$site - π SSR-generated SEO data
- π Prettier & ESLint
- π’ Pre-configured VSCode settings
The block-first approach is a core design decision: use Kirby's page structure as the source of truth without replicating it in Nuxt. All pages are rendered by the catch-all route.
If you need custom Kirby page blueprints with custom fields, you can create dedicated Nuxt pages and query the content using KQL. See 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://github.com/johannschopplich/cacao-kit-backend#bearer-token
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
β βββ proxy.ts # Development proxy utilities
βββ pages/
β βββ [...slug].vue # Universal page renderer
β βββ about.vue # Custom page example
βββ plugins/
β βββ site.ts # Global site data management
βββ queries/ # KQL query definitions
βββ index.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.vue and add your custom styles.
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
