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 .
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
Add your API token to .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 );
}
Update 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
<!-- 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 < PageFields >( 'landing_page' , route . params . slug as string );
if ( ! response . data ?. data ) {
throw createError ({ statusCode: 502 , statusMessage: 'Failed to load page from ButterCMS' });
}
return response . data . data ;
}
);
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 >
Collections
<!-- 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' ]);
if ( ! response . data ?. data ) {
throw createError ({ statusCode: 502 , statusMessage: 'Failed to load brands from ButterCMS' });
}
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 >
Dynamic components
Component Renderer
<!-- components/ComponentRenderer.vue -->
< script setup lang = "ts" >
import { defineAsyncComponent , type Component as VueComponent } from 'vue' ;
interface HeroComponent {
type : 'hero' ;
fields : {
headline : string ;
subheadline : string ;
image : string ;
button_label : string ;
button_url : string ;
};
}
interface FeaturesComponent {
type : 'features' ;
fields : {
headline : string ;
features : { title : string ; description : string ; icon : string }[];
};
}
interface TestimonialsComponent {
type : 'testimonials' ;
fields : {
headline : string ;
testimonials : { quote : string ; author : string ; role : string }[];
};
}
interface CtaComponent {
type : 'cta' ;
fields : {
headline : string ;
button_label : string ;
button_url : string ;
};
}
export type ButterComponent =
| HeroComponent
| FeaturesComponent
| TestimonialsComponent
| CtaComponent ;
defineProps <{
components : ButterComponent [];
}>();
const resolveComponent = ( type : ButterComponent [ 'type' ]) : VueComponent | null => {
switch ( type ) {
case 'hero' :
return defineAsyncComponent (() => import ( './Hero.vue' ));
case 'features' :
return defineAsyncComponent (() => import ( './Features.vue' ));
case 'testimonials' :
return defineAsyncComponent (() => import ( './Testimonials.vue' ));
case 'cta' :
return defineAsyncComponent (() => import ( './CTA.vue' ));
default :
return null ;
}
};
</ script >
< template >
< template v-for = " ( component , index ) in components " : key = " index " >
< component
v-if = " resolveComponent ( component . type ) "
: is = " resolveComponent ( 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
This uses a distinct page type (component_page) whose body is a Component Picker (Page Builder) field — an array of components — separate from the WYSIWYG landing_page in the Pages section (whose body is a string). A page’s body is one field type or the other, so the Page Builder example needs its own page type.
<!-- pages/landing/[slug].vue -->
< script setup lang = "ts" >
import type { ButterComponent } from '~/components/ComponentRenderer.vue' ;
const route = useRoute ();
const butter = useButter ();
interface PageFields {
body : ButterComponent [];
}
const { data : page , error } = await useAsyncData (
`landing- ${ route . params . slug } ` ,
async () => {
const response = await butter . page . retrieve < PageFields >( 'component_page' , route . params . slug as string );
if ( ! response . data ?. data ) {
throw createError ({ statusCode: 502 , statusMessage: 'Failed to load page from ButterCMS' });
}
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 } | null ;
}
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 });
if ( ! response . data ?. data ) {
throw createError ({ statusCode: 502 , statusMessage: 'Failed to load posts from ButterCMS' });
}
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 v-if = " post . author " > 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 | null ;
featured_image : string | null ;
author : { first_name : string ; last_name : string } | null ;
}
const { data : post , error } = await useAsyncData (
`post- ${ route . params . slug } ` ,
async () => {
const response = await butter . post . retrieve ( route . params . slug as string );
if ( ! response . data ?. data ) {
throw createError ({ statusCode: 502 , statusMessage: 'Failed to load post from ButterCMS' });
}
return response . data . data as Post ;
}
);
if ( error . value ) {
throw createError ({ statusCode: 404 , message: 'Post not found' });
}
const publishedDate = computed (() =>
post . value ?. published ? new Date ( post . value . published ). toLocaleDateString () : null
);
</ script >
< template >
< article v-if = " post " >
< h1 > {{ post . title }} </ h1 >
< p >
< span v-if = " post . author " > By {{ post . author . first_name }} {{ post . author . last_name }} </ span >
< span v-if = " publishedDate " > on {{ publishedDate }} </ span >
</ 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 ();
interface Seo {
title ?: string ;
description ?: string ;
og_title ?: string ;
og_description ?: string ;
og_image ?: string ;
}
interface PageFields {
headline : string ;
seo ?: Seo ;
}
const { data : page } = await useAsyncData (
`page- ${ route . params . slug } ` ,
async () => {
const response = await butter . page . retrieve < PageFields >( 'landing_page' , route . params . slug as string );
if ( ! response . data ?. data ) {
throw createError ({ statusCode: 502 , statusMessage: 'Failed to load page from ButterCMS' });
}
return response . data . data ;
}
);
const seo = computed < Seo >(() => 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