Connecting a Headless CMS to Vev

October 25, 2021

Words by Parker Tinsley & Nicolay Thafvelin

Vev has proven itself as a powerful, interactive front-end builder but what happens when your team needs a CMS solution?

We’ve gotten this question a lot (a lot, a lot). Most teams have an intimate knowledge of Vev’s Design Editor and thoroughly enjoy working in a no-code, visual tool. But when it comes to Vev’s Code Editor, they struggle to take full advantage of its incredible flexibility to serve up dynamic content by connecting to a headless CMS like Contentful.

That’s why we are going to break it down for you and show you exactly how we did it on our showcase page.

Light, Product, Font, Line, Screenshot

The Vev Showcase

Our team is proud to say our website was built 99% fully visually and without any code but let’s talk about that 1%. When we first outlined the requirements for our website, we knew the showcase page could only be managed through a CMS. We knew this page would grow exponentially to store hundreds (if not thousands) of websites built in Vev and we needed a way to easily update and manage that content.

Thankfully, Vev’s CTO Nicholay chimed in and said ‘Oh that’s easy.’

We then set off on our journey to serve up dynamic content on our showcase page and use Contentful to store the data. Chances are, your team is looking to do the same — so let’s break down how we did it step-by-step.

Step 1: Setting up your Content Model

Contentful data operates from a content model, where you can insert and call virtually anything. Once you establish your content model, you can then start adding your content based on the fields you specify.

Our first step was to define how many content models and components we would need, plus the specifics for each one. As a team we mapped out that we would need the following:

  1. Category Filter: To allow the user to navigate between the different categories/tags
  2. Showcase Intro: To display the title and description of the category
  3. Showcase Grid: To list all the websites built in Vev

Content Model 1: Category Filter

This, in fact, would not need to be managed by a Contentful content model but rather the tagging system within Contentful. For the showcase, we established tags such as:

  1. Event
  2. Storytelling
  3. Microsite
  4. Marketing
  5. Portfolio
  6. The nice thing? We can always add more tags if we choose to evolve these categories in the future.

Content Model #2: Showcase Intro

Similar to a category filter, we wanted to expose the title of the category and a small description in a captivating way to the user.

We love the way this turned out and from a content model perspective we only needed two content fields; a title and a description.

    Content Model #3: Showcase Grid

    The grid was always the main concern. How do we create a dynamic showcase grid that is categorized and make it paginate? Turns out, that’s fairly easy with Contentful and a little code.

    Our content model for each grid item consisted of:

  1. Image
  2. Title
  3. Description (or author)
  4. Featured (Yes/No): We used this function so that certain grid items would appear in the featured large layout
  5. URL
  6. Now that we had created the content models and settled the tag structure, we would need to connect to Contentful and start developing our components.

Step 2: Connecting the Contentful API to Vev’s Code Editor

In order to connect Vev to Contentful, we needed to use Contentful’s API to pull the models and content data we outlined above. To do this in Vev’s Code Editor is easier than you might think, since each Vev project gets its own tiny react application. Here we can utilize everything as you would have done developing on your own machine.

So through some simple fetches and react hooks, we can fetch content from our Contentful setup.

Authenticate to Fetch from Contentful API

Make sure to pass your access token in the Authorization header or access_token parameter in the fetch request, more details on how to generate and use this token can be found here.

An example of this would look like:

1async function fetchContent(url){
2  const res = await fetch(url, {
3      headers: { 'Authorization': 'Bearer your-public-access-token' }
4  })
5  const json = await res.json();
1const entryPromise = fetchContent(`${space}/entries`)

Fetching Data from the Contentful API

For this example, we will not look into pagination. We will fetch all the content in our Contentful space. This can be fetched from Contentful through the Entries collection URL.

As you will see in the Contentful docs you can add an environment to the URL like{space_id}/entries but this is not required.

An example of this would look like:

Create React hooks to Make it Easy to Use

To make it easy to use the data you fetched from the API, you should make some react hooks. This handles the loading state as well.

1export function useShowcases() {
2  const [list, setShowcases] = useState<Showcase[]>(showcases);
3  useEffect(() => {
4    // In the transformer we set the showcaseLoaded, so if not set we neeed to wait for this
5    if (!showcasesLoaded) await entryPromise.then(({ showcases }) => setShowcases(showcases))
6    else setShowcases(showcases);
7  }, []);
9  return [showcasesLoaded, list];

Transform the Content into Something Usable

The result you get back from Contentful API contains a lot of metadata that we don’t need when rendering our JSX. So we recommend creating a transformer for the result to create ‘models’ and ‘collections’ based on the result.

So in our case, we want to create a collection of showcases, and showcase tags.

1let showcaes:Showcase = [];
2let showcaseTags:ShowcaseTags = [];
3let showcasesLoaded:boolean = false;
4entryPromise.then((apiRes) => {
5  showcasesLoaded = true;
6  for (const item of r.items) {
7    // In case you have multiple content types in contentfull
8    if( === 'showcase'){
9      // Creating list of tags
10      const tags = (item.metadata?.tags || []).map(t =>;
11      showcase.push({
12        id:,
13        title: item.fields.title,
14        description: item.fields.description,
15        image: item.fields.image?, // Asset id
16        tags,
17      });
19      for (const tag of tags) {
20          // Push tags which is not yet added
21          if(showcaseTags.indexOf(tag) === -1) showcaseTags.push(tag);
22      }
23    }
24  }

Step 3: Creating Dynamic Components

Now we can finally render something. We can add our useShowcases hook to a react component and render out our ShowcaseItem.

1export function ShowcaseList(){
2  const [loaded, showcases] = useShowcases();
3  if(!loaded) return <div className="loader">Loaindg content<div>
4  return (
5    <div class="grid">
6      { => <ShowcaseItem key={} item={item} />)}
7    </div>
8  )
11function ShowcaseItem({item}){
12  const image = useContentfulImage(item.image);
13  return (
14    <div className="item">
15      <img src={img?.url} width={300} />
16      <h3>{item.title}</h3>
17      <p>{item.description}</p>
18    </div>
19  )

Handling Assets from Contentful

Assets added to your entries in Contentful are stored as a reference to the asset collections, not as a URL.

To be able to use the asset URL, we need to fetch the asset collection. This can be done by using the previous method. We can create an asset promise (to the asset URL) and create a hook to find use the desired image.

1const assetsPromise = fetchContent(`${space}/assets`).then((res) => {
2  for (const item of res.items) {
3    images.push({
4      id:,
5      title: item.fields.title,
6      url: item.fields.file?.url,
7    })
8  }
11export function useContentfulImage(imageId: string) {
12  const [image, setImage] = useState(() => images.find(i => === imageId));
13  useEffect(() => {
14    if (!image) assetsPromise.then(() => setImage(images.find(i => === imageId)));
15  }, [imageId])
16  return image;

Now we can add assets to our component by using the useContentfulImage hook. We can also update our ShowcaseItem to use this hook.

1function ShowcaseItem({item}){
2  const image = useContentfulImage(item.image);
3  return (
4    <div className="item">
5      <img src={img?.url} />
6      <h3>{item.title}</h3>
7      <p>{item.description}</p>
8    </div>
9  )

Finally, we need to style our components

Stying in Vev is extremely powerful since every component comes with styles.scss file and we can utilize what we call the vev() macro to make the CSS attribute available to the designer to change.

We can add styling directly in the styles.scss file for our items but don’t worry about the selectors, the Vev compiler makes sure the styles do not apply anywhere else on the site.

However, by using the vev() macro we can eliminate the back-and-forth between a designer and developer by letting the designer choose the styling directly in the Design Editor.

2  text-align: center;
3  color:vev(#999);
4  font-size: vev(40px);
5  font-family: vev(Lato);
6  letter-spacing:vev();
10  display:flex;
11  flex-gap:30px;
15  background: vev(white);
16  border:vev();
17  box-shadow:vev();
18  width:300px;
19  h3{
20    font-size:vev(24px);
21    font-family:vev(Lato);
22    color:vev(black);
23  }
24  p {
25    font-size:vev(24px);
26    font-family:vev(Lato);
27    color:vev(#333);
28  }
30  img{
31    width: 100%;
32    height:200px;
33    object-fit:cover;
34  }

Step 4: Add the components to the canvas and publish

Once we’ve done the work of creating the components, they will be available in the Add Menu just like any of our public features. But don’t worry these are private to your account!

The best part? You can look to reuse the same component on other sites and even fork the code package for other projects.

We hope this helps you and your team to build out dynamic components and solves those headaches.

If you have any other questions, feel free to reach out to our Customer Success team directly in the platform or email us at

Rectangle, Triangle

Want More Inspo?

Get our monthly newsletter straight to your inbox.
You can always unsubscribe at any time.
Privacy Policy

Rectangle, 3D, Green
Youtube icon
Linkedin icon
Instagram icon
Facebook icon
Twitter icon

This site was built in Vev © 2021 Vev. All rights reserved.