Getting Started with ButterCMS in Nimara Storefront: A Step-by-Step Guide for Developers
If you’re working with Nimara Storefront, you probably already know it’s built to be flexible. One of the key features is its CMS-agnostic setup. So you can choose which content management system powers different parts of your storefront.
Currently, Nimara supports ButterCMS and Saleor CMS. Switching between them is surprisingly simple — but more on that in a moment.
How to Switch to ButterCMS in the Nimara Storefront?
Switching to ButterCMS in Nimara Storefront is designed to be as simple as possible. Below, we’ll walk you through the steps to get everything up and running smoothly.
Once you're set up, learn how to add and manage content in ButterCMS for Nimara Storefront.
Step 1 — Connect ButterCMS to Nimara Storefront
First things first: if you want Nimara to pull content from Butter CMS, it needs permission.
That means you must grab your API key, which you can easily find in the ButterCMS dashboard under Settings:
Once you have it, add this to your environment variables:
NEXT_PUBLIC_BUTTER_CMS_API_KEY=<your-super-secret-api-key>
No key, no content. It’s that simple.
Step 2 — Tell Nimara You Want Butter (Not Saleor)
Nimara’s flexibility comes from its CMS service abstraction layer. It means you can choose your content source using a simple environment variable.
To use Butter CMS, you just need to set the following in your Vercel project’s Environment Variables (under Settings):
When you get it, add this to your environment variables (just for local testing):
CMS_SERVICE=butter
This tells the storefront: "Hey, all my content should come from Butter CMS."
Note: Changing this variable alone doesn't migrate content — it just changes the source. Make sure your content actually exists in the chosen CMS before flipping the switch.
Step 3 - Redeploy
Whenever you change CMS_SERVICE
, you must redeploy your Vercel project and make sure the new provider is fully applied.
This step is crucial for making the integration work seamlessly, as it updates the environment with the latest CMS configuration.
What You Can Handle with ButterCMS in Nimara?
Once you connect ButterCMS, you can manage several key parts of your storefront directly from the ButterCMS dashboard — no code changes are required.
Currently, Butter CMS Powers No-Code Content Updates within:
- Navigation - Main menu with categories, collections, or custom links.
- Footer - All those useful (and sometimes legally required) links at the bottom.
- Homepage - Key sections like product carousels, banners, and promotional sections.
This makes it easy for non-technical teams to update critical parts of the storefront without asking developers for help — and that’s always a win.
Why the Freedom to Switch CMSs Matters?
The beauty of Nimara’s setup is that you’re not locked into a single CMS.
Whether your project starts with Saleor’s built-in CMS or you decide to switch to ButterCMS later, the process is smooth.
You just need to update your environment variables and redeploy - and your content source switches seamlessly.
How to Customize Your CMS Setup?
If you want to get fancy or are in one of those "we're migrating, but not all at once" situations, you can mix and match your CMS sources directly in the code.
You’ll find Nimara’s provider setup in the same file where cmsPageService
and cmsMenuService
are defined:
import { invariant } from "ts-invariant";
import type { CMSMenuService } from "@nimara/infrastructure/use-cases/cms-menu/types";
import type { CMSPageService } from "@nimara/infrastructure/use-cases/cms-page/types";
import { clientEnvs } from "@/envs/client";
const isButterCMS = clientEnvs.CMS_SERVICE === "butter";
const getCMSPageService = async (): Promise<CMSPageService> => {
if (isButterCMS) {
invariant(
clientEnvs.NEXT_PUBLIC_BUTTER_CMS_API_KEY,
"ButterCMS API key is required but not provided. Please set NEXT_PUBLIC_BUTTER_CMS_API_KEY in the environment variables.",
);
const { butterCMSPageService } = await import(
"@nimara/infrastructure/public/butter-cms/cms-page/providers"
);
return butterCMSPageService({
token: clientEnvs.NEXT_PUBLIC_BUTTER_CMS_API_KEY,
});
} else {
const { saleorCMSPageService } = await import(
"@nimara/infrastructure/public/saleor/cms-page/providers"
);
return saleorCMSPageService({
apiURL: clientEnvs.NEXT_PUBLIC_SALEOR_API_URL,
});
}
};
const getCMSMenuService = async (): Promise<CMSMenuService> => {
if (isButterCMS) {
invariant(
clientEnvs.NEXT_PUBLIC_BUTTER_CMS_API_KEY,
"ButterCMS API key is required but not provided. Please set NEXT_PUBLIC_BUTTER_CMS_API_KEY in the environment variables.",
);
const { butterCMSMenuService } = await import(
"@nimara/infrastructure/public/butter-cms/cms-menu/providers"
);
return butterCMSMenuService({
token: clientEnvs.NEXT_PUBLIC_BUTTER_CMS_API_KEY,
});
} else {
const { saleorCMSMenuService } = await import(
"@nimara/infrastructure/public/saleor/cms-menu/providers"
);
return saleorCMSMenuService({
apiURL: clientEnvs.NEXT_PUBLIC_SALEOR_API_URL,
});
}
};
export const cmsPageService: CMSPageService = await getCMSPageService();
export const cmsMenuService: CMSMenuService = await getCMSMenuService();
It loads either Saleor or Butter services by default based on the CMS_SERVICE
environment variable.
But if you need more control, you can override this logic and hardcode different providers for specific parts of the storefront.
This is how you get homepage from Butter CMS and navigation & footer form Saleor:
const getCMSPageService = async (): Promise<CMSPageService> => {
invariant(
clientEnvs.NEXT_PUBLIC_BUTTER_CMS_API_KEY,
"ButterCMS API key is required but not provided. Please set NEXT_PUBLIC_BUTTER_CMS_API_KEY in the environment variables.",
);
const { butterCMSPageService } = await import(
"@nimara/infrastructure/public/butter-cms/cms-page/providers"
);
return butterCMSPageService({
token: clientEnvs.NEXT_PUBLIC_BUTTER_CMS_API_KEY,
});
};
const getCMSMenuService = async (): Promise<CMSMenuService> => {
const { saleorCMSMenuService } = await import(
"@nimara/infrastructure/public/saleor/cms-menu/providers"
);
return saleorCMSMenuService({
apiURL: clientEnvs.NEXT_PUBLIC_SALEOR_API_URL,
});
};
export const cmsPageService: CMSPageService = await getCMSPageService();
export const cmsMenuService: CMSMenuService = await getCMSMenuService();
What’s Possible with a Modular CMS Setup?
- You can pull navigation and the footer from Saleor
- While using ButterCMS for the homepage, banners, and static pages.
- Or… flip it around if you enjoy giving your future self a headache.
NEXT_PUBLIC_BUTTER_CMS_API_KEY
How Nimara Uses Serialization to Make ButterCMS (and Any CMS) Work Seamlessly?
By default, Nimara Storefront ships with Saleor CMS baked in. That’s the natural pairing since Saleor handles products, categories, collections, and pages directly.
But what happens when you want more flexibility — maybe a marketing team-friendly system like ButterCMS for content management?
The key to making this work is serialization.
Why Serialization Exists (and Why You Should Care)
You know the pain if you've ever tried plugging two completely different systems into the same frontend. Each platform has its own:
- data structure,
- naming conventions,
- relationship models (or lack thereof).
Without serialization, every component in Nimara would need to know exactly which CMS is in use and handle both data structures with a bunch of ugly
if (cms === "butter")
checks.
That’s not how we roll.
Instead, Nimara forces every CMS (Saleor, Butter, or whatever’s next) to speak the same language. The frontend never knows (or cares) where the data came from.
Butter CMS — Flexible, but Needs Structure
ButterCMS is a powerful general-purpose CMS that allows you to structure content your way. Since it doesn't enforce a standard "menu concept", Nimara builds the main navigation by combining:
- a collection for top-level items (main navigation);
- a second collection for second-level items (submenus).
These two collections aren’t automatically linked in Butter CMS — that’s Nimara’s job. The Butter CMS menu infrastructure layer:
- fetches both collections;
- matches submenus to their parent items;
- passes everything through a serializer that normalizes it into Nimara’s standard menu format.
When the data reaches the frontend, it's just a standard menu tree with items, labels, URLs, and optional children — the same as if it came from Saleor.
Saleor — The Default, but Not the Only One
Since Saleor CMS is product-focused, it ships with a menu system that knows about categories, collections, and pages. That’s why Nimara defaults to Saleor.
But even Saleor's menus get serialized because Saleor's idea of a "menu item" isn't quite the same as Butter's — and Nimara wants every menu to look precisely the same by the time it hits the component.
The Unified Menu Shape
Whether your menu comes from ButterCMS or Saleor, the component always gets the same shape:
export type MenuItem = {
children?: MenuItemChild[] | null;
id: string;
label: string;
url: string;
};
No CMS-specific quirks.
No conditionals.
Just clean, predictable data.
Serialization Is a Must - Raw Data Won’t Work
This is a non-negotiable rule in Nimara. No raw Butter CMS data. No raw Saleor responses.
Everything gets:
- fetched by the infrastructure layer;
- transformed by a serializer;
- delivered to the component in the standard shape.
This approach follows clean architecture principles, separating concerns at each layer:
This is how Nimara stays CMS-agnostic and why switching between Saleor and ButterCMS (or back) doesn’t require rewriting your components.
With serialization, Nimara stays flexible, future-proof, and CMS-agnostic.
Switching CMSs Should Be Easy — So We Made It That Way
At the end of the day, Nimara’s goal is to give you flexibility without the usual integration headaches. We’ve done the hard part for you — building a CMS-agnostic system, normalizing data, and handling serialization.
That means you don’t have to spend hours wrangling inconsistent APIs or rewriting components. Just set your environment variables, redeploy, and you’re good to go.
Have Questions or Ideas?
Drop us a message on Discord - we can’t wait to hear from you!