Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.
Starter project
Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working starter project that’s integrated with content already inside of your ButterCMS account.Nuxt Starter Project
Hit the ground running with a pre-configured Nuxt + ButterCMS setup.
Installation
- npm
- yarn
- pnpm
npm install buttercms
yarn add buttercms
pnpm add buttercms
.env:
NUXT_PUBLIC_BUTTER_CMS_API_KEY=your_api_token
Initialize the client
Create a composable for the ButterCMS client:// composables/useButter.ts
import Butter from 'buttercms';
export function useButter() {
const config = useRuntimeConfig();
return Butter(config.public.butterCmsApiKey);
}
nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
butterCmsApiKey: process.env.NUXT_PUBLIC_BUTTER_CMS_API_KEY,
},
},
});
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
- Nuxt 3
- Nuxt 2
<!-- pages/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const butter = useButter();
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
const { data: page, error } = await useAsyncData(
`page-${route.params.slug}`,
async () => {
const response = await butter.page.retrieve('landing-page', route.params.slug as string);
return response.data.data as { fields: PageFields };
}
);
if (error.value) {
throw createError({ statusCode: 404, message: 'Page not found' });
}
</script>
<template>
<main v-if="page">
<h1>{{ page.fields.headline }}</h1>
<p>{{ page.fields.subheadline }}</p>
<img v-if="page.fields.hero_image" :src="page.fields.hero_image" :alt="page.fields.headline" />
<div v-html="page.fields.body" />
</main>
</template>
<!-- pages/_slug.vue -->
<template>
<main v-if="page">
<h1>{{ page.fields.headline }}</h1>
<p>{{ page.fields.subheadline }}</p>
<img v-if="page.fields.hero_image" :src="page.fields.hero_image" :alt="page.fields.headline" />
<div v-html="page.fields.body" />
</main>
</template>
<script>
import Butter from 'buttercms';
const butter = Butter(process.env.BUTTER_CMS_API_KEY);
export default {
async asyncData({ params, error }) {
try {
const response = await butter.page.retrieve('landing-page', params.slug);
return { page: response.data.data };
} catch (err) {
error({ statusCode: 404, message: 'Page not found' });
}
},
};
</script>
Collections
- Nuxt 3
- Nuxt 2
<!-- pages/brands.vue -->
<script setup lang="ts">
const butter = useButter();
interface Brand {
name: string;
logo: string;
description: string;
}
const { data: brands } = await useAsyncData('brands', async () => {
const response = await butter.content.retrieve(['brands']);
return response.data.data.brands as Brand[];
});
</script>
<template>
<main>
<h1>Our Brands</h1>
<ul>
<li v-for="(brand, index) in brands" :key="index">
<img :src="brand.logo" :alt="brand.name" />
<h2>{{ brand.name }}</h2>
<div v-html="brand.description" />
</li>
</ul>
</main>
</template>
<!-- pages/brands.vue -->
<template>
<main>
<h1>Our Brands</h1>
<ul>
<li v-for="(brand, index) in brands" :key="index">
<img :src="brand.logo" :alt="brand.name" />
<h2>{{ brand.name }}</h2>
<div v-html="brand.description" />
</li>
</ul>
</main>
</template>
<script>
import Butter from 'buttercms';
const butter = Butter(process.env.BUTTER_CMS_API_KEY);
export default {
async asyncData() {
const response = await butter.content.retrieve(['brands']);
return { brands: response.data.data.brands };
},
};
</script>
Dynamic components
Component Renderer
<!-- components/ComponentRenderer.vue -->
<script setup lang="ts">
import { defineAsyncComponent, type Component as VueComponent } from 'vue';
interface ButterComponent {
type: string;
fields: Record<string, any>;
}
defineProps<{
components: ButterComponent[];
}>();
const componentMap: Record<string, VueComponent> = {
hero: defineAsyncComponent(() => import('./Hero.vue')),
features: defineAsyncComponent(() => import('./Features.vue')),
testimonials: defineAsyncComponent(() => import('./Testimonials.vue')),
cta: defineAsyncComponent(() => import('./CTA.vue')),
};
</script>
<template>
<template v-for="(component, index) in components" :key="index">
<component
v-if="componentMap[component.type]"
:is="componentMap[component.type]"
v-bind="component.fields"
/>
</template>
</template>
Example Component
<!-- components/Hero.vue -->
<script setup lang="ts">
defineProps<{
headline: string;
subheadline: string;
image: string;
button_label: string;
button_url: string;
}>();
</script>
<template>
<section class="hero">
<h1>{{ headline }}</h1>
<p>{{ subheadline }}</p>
<a v-if="button_label" :href="button_url">{{ button_label }}</a>
<img v-if="image" :src="image" :alt="headline" />
</section>
</template>
Using in Pages
<!-- pages/landing/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const butter = useButter();
const { data: page, error } = await useAsyncData(
`landing-${route.params.slug}`,
async () => {
const response = await butter.page.retrieve('landing-page', route.params.slug as string);
return response.data.data;
}
);
if (error.value) {
throw createError({ statusCode: 404, message: 'Page not found' });
}
</script>
<template>
<main v-if="page">
<ComponentRenderer :components="page.fields.body" />
</main>
</template>
Blog
- Blog Post List
- Single Blog Post
<!-- pages/blog/index.vue -->
<script setup lang="ts">
const route = useRoute();
const butter = useButter();
interface Post {
slug: string;
title: string;
summary: string;
author: { first_name: string; last_name: string };
}
const page = parseInt(route.query.page as string) || 1;
const { data } = await useAsyncData(`blog-page-${page}`, async () => {
const response = await butter.post.list({ page, page_size: 10 });
return {
posts: response.data.data as Post[],
meta: response.data.meta,
};
});
</script>
<template>
<main v-if="data">
<h1>Blog</h1>
<ul>
<li v-for="post in data.posts" :key="post.slug">
<h2><NuxtLink :to="`/blog/${post.slug}`">{{ post.title }}</NuxtLink></h2>
<p v-html="post.summary" />
<span>By {{ post.author.first_name }} {{ post.author.last_name }}</span>
</li>
</ul>
<NuxtLink v-if="data.meta.next_page" :to="`/blog?page=${data.meta.next_page}`">
Next Page
</NuxtLink>
</main>
</template>
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const butter = useButter();
interface Post {
title: string;
body: string;
published: string;
featured_image: string;
author: { first_name: string; last_name: string };
}
const { data: post, error } = await useAsyncData(
`post-${route.params.slug}`,
async () => {
const response = await butter.post.retrieve(route.params.slug as string);
return response.data.data as Post;
}
);
if (error.value) {
throw createError({ statusCode: 404, message: 'Post not found' });
}
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<p>
By {{ post.author.first_name }} {{ post.author.last_name }} on
{{ new Date(post.published).toLocaleDateString() }}
</p>
<img v-if="post.featured_image" :src="post.featured_image" :alt="post.title" />
<div v-html="post.body" />
<NuxtLink to="/blog">Back to Posts</NuxtLink>
</article>
</template>
Preview Mode
Enable content editors to preview draft content:// composables/useButter.ts
import Butter from 'buttercms';
export function useButter() {
const config = useRuntimeConfig();
const route = useRoute();
const isPreview = route.query.preview === 'true';
return Butter(config.public.butterCmsApiKey, isPreview);
}
SEO
<!-- pages/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const butter = useButter();
const { data: page } = await useAsyncData(
`page-${route.params.slug}`,
async () => {
const response = await butter.page.retrieve('landing-page', route.params.slug as string);
return response.data.data;
}
);
const seo = computed(() => page.value?.fields.seo || {});
useHead({
title: () => seo.value.title || page.value?.fields.headline,
meta: [
{ name: 'description', content: () => seo.value.description },
{ property: 'og:title', content: () => seo.value.og_title || seo.value.title },
{ property: 'og:description', content: () => seo.value.og_description || seo.value.description },
{ property: 'og:image', content: () => seo.value.og_image },
],
});
</script>
Resources
Nuxt Starter
Pre-configured starter project
JavaScript SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation