Transforming Vimeo’s content landscape with Builder.io

It wasn’t just a choice but a strategic necessity to build a CMS for our marketing teams.

Shivangi Jain
Vimeo Engineering Blog
12 min readApr 18, 2024

--

Content management systems, or CMS, are the software applications that provide an interface for users to create, edit, organize, and publish content without requiring extensive technical knowledge or programming skills. They are the proven linchpins for various organizations due to their unique selling points of a user-friendly interface, efficient version control, customizable features, support for internationalization, experimentation, incorporation of search engine optimization (SEO) tools, and effortless responsive design creation.

But not all content management systems are created equal. In this post, we journey through how we determined the limitations of our previous CMS and recognized the need for a better solution, why we adopted Builder.io specifically, the ways in which we introduced and streamlined it into our architecture, how we fared, and what we’re doing now.

Determining the limitations of our previous systems

Our journey started by experimenting with over 500 Vimeo marketing pages that focused mostly on businesses. Roughly three years ago, these pages were constructed using a combination of WordPress and Instapage, and a few were developed internally within the Vimeo repository as React pages. While using WordPress and Instapage didn’t rely on the engineering team, there were numerous drawbacks to using these two CMS tools that outweighed their benefits:

  • Tedious internationalization. Vimeo currently supports seven distinct languages, necessitating the translation of all pages and image text before their release to users. Achieving this through WordPress and Instapage proved challenging. For example, Instapage demanded the creation of separate pages for each language, which was both a laborious and financially burdensome process, and made it very tedious to keep the pages in sync.
  • Limited tracking. Tracking is a pivotal element to our webpage creation, offering invaluable insights into feature performance and user behavior. This data serves as a compass, guiding our future improvements and addressing user needs. Vimeo employs tracking through Fatal Attraction and our in-house system, Big Picture, which are explored in depth in a previous blog post. Using our former page creation tools meant relying on Google Analytics for tracking, which limited customizability and flexibility in our tracking flow.
  • Misaligned approach to A/B testing. We use A/B testing to determine the better performing version of a webpage or any feature, to assess which version yields better results in terms of user engagement, conversions, or other predefined metrics. While the former systems offered some support and specific plugins for testing, they didn’t align with our in-house approach to implementing A/B testing.
  • Substandard quality and responsiveness. Vimeo works towards achieving not only functional alignment of a feature with the user, but also its visual appeal, user friendliness, and the webpage’s response time. One such component of visual appeal is breakpoints. Along with standard breakpoints, there are certain components that require custom breakpoints on our pages. Adding custom breakpoints to only some sections of the page was difficult with these tools. Additionally, because these marketing pages were built long ago and didn’t receive updates, there were resulting issues of unresponsiveness, text overlaps, and misaligned images on specific resolutions (see Figure 1).
Figure 1. An enterprise page illustrating a form overlapping the next section when minimizing the screen width.
  • Poor search engine and performance optimizations. Beyond visibility in search engines, SEO contributes to user experience, credibility, and overall website performance. By optimizing content, meta tags, site structure, and other elements, SEO aims to attract organic traffic — users who find the site through search results. Our old CMS tools mostly required plugins to implement even basic SEO functions, which gave us the unwanted results of improper image sizes for different resolutions, improper and missing metadata, extremely low page speeds, and more.
  • Improper image handling for different resolutions. Images play a pivotal role in determining website page speeds. Their size, format, and optimization directly impact loading times, affecting user experience and SEO. Large, unoptimized images can significantly slow down page loading, causing delays in rendering content. On the other hand, images that are properly sized and optimized contribute to faster load times, ensuring a smoother browsing experience. The marketing pages we experimented with suffered from improperly sized images. One such page, shown in Figure 2, contained an image sized at 1 MB, with dimensions of 5255 ✕ 3518 pixels, yet displayed at a mere 272 ✕ 182 pixels for mobile views. This significant disparity substantially contributed to a low score of 8–10 percent on Google’s PageSpeed Insights.
Figure 2. A Vimeo Experts page containing an image sized 1 MB, where an intrinsic size of 5255 ✕ 3518 pixels results in a rendered display size of 272 ✕ 182 pixels on a mobile view.

While these were the challenges we faced when attempting to publish a well-performing page with WordPress and Instapage, the pages we created using React inside of the Vimeo repository were also returning scores of 20–30 percent on PageSpeed Insights, owing to client-side rendering.

This experiment helped us determine the limitations of the systems we were using, and made us recognize the precise needs that nudged us toward seeking a better solution.

Journeying through the solution

We started our problem-solving journey by narrowing our scope to improving the performance of the marketing pages that were created with React. Remember that these pages were affected by client-side rendering, which does offer benefits in the forms of flexibility and interactivity, but can also impact page performance due to its processing nature.

Here’s what we set out to do:

  • Remove or limit the usage of third-party apps to only the places in which they were required.
  • Remove unused JavaScript and CSS.
  • Introduce the usage of our in-house optimized video player.
  • Add different dimensions of optimized images and render them on their respective viewports.
  • Enhance caching.

To achieve these goals while continuing to use React, we rebuilt some of the important marketing pages using Next.js because of its ability to perform automatic code splitting, server-side rendering, static site generation (also known as SSG; more on this later), improved SEO, and more. This helped us raise the PageSpeed Insights score from 30–40 percent to 85–95 percent on desktop devices — a step in the right direction!

We realized that the next step was to enable our marketing teams to independently construct pages, launch paid campaigns, and create landing pages without continual reliance on engineering resources, which meant finding ourselves a what-you-see-is-what-you-get editor. Why? Because empowering the marketing teams to efficiently edit and manage page content autonomously would ensure streamlined operations and faster responsiveness to market needs, resulting in both optimized resource allocation and fostered agility and efficiency within the marketing and development workflows. This is what led us to Builder.io.

Embracing Builder.io for seamless page creation

After evaluating various third-party editors, we concluded that Builder.io was the optimal choice to proceed with. It fulfilled most of our requirements, encompassing crucial features such as support for localization, A/B testing capabilities, seamless integration with our tracking mechanisms, version history, preview functionalities, and straightforward rollback options. Its drag-and-drop functionality and a user-friendly interface would make it easy for any non-technical user to work with.

Builder.io operates as a headless CMS with a visual editor, enabling users to create and manage content for websites and applications. Users design content visually using a drag-and-drop interface, which we show you in the video below. They can create layouts, add elements like text, images, buttons, and forms, and customize the appearance and functionality of these components without extensive coding. Builder.io enables users to bind dynamic data to elements, which means that content can be linked to various data sources, including APIs or databases, making it adaptable and responsive to real-time updates. Users can also tailor an entire responsive website by dragging and dropping either the custom created elements and components or the ones provided by Builder.io. Text, images, videos, color…you name it. All of this can be easily added to the user’s webpage without the requirement of writing the actual code! Once users are done designing, they can click the Publish button to ensure that the latest iteration of the content becomes visible to other users, while enabling version control and tracking for future modifications. The following video provides an example:

Transitioning to Builder.io

Integrating Builder.io into the Vimeo architecture was done in phases, as illustrated in Figure 3. The engineering team followed the Vimeo templates to build all the components, which accept data in the form of props. Props enable users to have full control over what they want on the webpage and when. Additionally, these components support SEO and tracking, which take in values in the form of props. All of these components are contained in a marketing repository that’s separate from the main Vimeo repository.

Figure 3. An overview of the infrastructure setup for integrating Builder.io into Vimeo’s architecture.

Here’s how the process went:

  1. The marketing pages that we developed using Builder.io utilized the code stored in a separate repository, namely the marketing repository, housing the shell and critical React components that formed the page structure.
  2. We added some Slack commands to trigger deployment. Upon trigger, Jenkins initiated the build process by cloning the marketing repository and executing the marketing build.
  3. Within the build, data retrieval from Builder.io occurred via the Builder.io API. The process involved utilizing Next.js static site generation to generate static HTML files from the acquired content.
  4. Builder.io’s edit URL, hosted on Vimeo’s platform, pulled page content from Google Cloud Storage, or GCS, after localization during the deployment process.
  5. Following generation, the resulting HTML and JavaScript files were transferred and stored in the specified GCS bucket.
  6. For users accessing these pages, content retrieval was facilitated through the marketing controller within Vimeo’s framework, directly extracting pages from the GCS bucket for display and interaction purposes.

Streamlining the process

Although the transition to Builder.io was mostly a success, we did hit one bump in the road that required some thinking on our feet to streamline the process. Our Vimeo Careers page consists of a set of pages that list and provide information about the current job openings at Vimeo. These pages are also constructed using Builder.io; however, the approach to data handling differs. In this case, data retrieval is achieved through the Greenhouse API. Therefore, we opted to fetch this data during the build time rather than at run time.

Given that we were already utilizing Next.js, implementing data fetching at build time, commonly known as static site generation, was a straightforward process. Leveraging Next.js’ Incremental Static Regeneration feature facilitated the regeneration of static pages in the background, alleviating concerns about outdated or stale content.

To streamline the process, we devised a custom hook called useQueryCMS, which essentially aggregates all endpoints into a global queue. These endpoints are automatically lifted up to the page’s getStaticProps function and are fetched when the page is pre-rendered. The data is fetched only at pre-render, never on the client, and rendered to static HTML. We benefit from all the advantages of getStaticProps, because indirectly that’s what we’re using.

How does this actually work? We describe the process in three steps.

Step 1. Resolving API requests

We created a function that gathers and fetches these endpoints. This function uses ReactDOM’s renderToStaticMarkup to perform an extra page render prior to the Next.js pre-render. During this extra render, the endpoints are pushed to a global array.

The fetched data is added to an object, where its key is the requested endpoint. This object is passed into the page props. Because the key is the requested endpoint, it can be mapped back to the respective component when the page is rendered. Here’s an example:

while (true) {
global.__next_ssg_requests = [];
ReactDOMServer.renderToStaticMarkup(<Page {…extraProps} __next_ssg_data={__next_ssg_data} />);
// all data dependencies resolved
if (!global.__next_ssg_requests.length) break;
// dedupe requests
const endpoints = Array.from(new Set(global.__next_ssg_requests)) as string[];
try {
// fetch data and set the data, render again
await Promise.all(
endpoints.map((endpoint) => {
if (!pendingPromises[endpoint]) {
pendingPromises[endpoint] = fetch(endpoint)
.then((res) => {
if (!res.ok) {
delete pendingPromises[endpoint];
throw res.status;
}
return res.json();
})
.then((data: unknown) => {
__next_ssg_data[endpoint] = data;
delete pendingPromises[endpoint];
});
}
return pendingPromises[endpoint];
}),
);
} catch (err) {
// throw error when fetching data
throw err;
}
}

Step 2. Initializing context with data

A context is initialized using the fetched data from the page props. Components nested at any level can then read the data returned from their assigned endpoint, like this:

if (!__next_ssg_data) return <>{children}</>;
return (
<QueryCMSContext.Provider value={__next_ssg_data || {}}>{children}</QueryCMSContext.Provider>
);

Step 3. Rendering components

Components using this data render in two different phases: the additional rendering phase and the standard rendering phase.

In the additional rendering phase, the designated endpoint is appended to a global array. No user interface rendering takes place during this extra render, as its sole purpose is to elevate the necessary endpoints to the getStaticProps function.

In a standard rendering phase, the component retrieves the data from the context, subsequently transmitting it to its child render function.

To learn more about this type of rendering and data retrieval, check out the Next.js documentation on getStaticProps.

How we fared

How’d we fare? Very well, that’s how. Our success is most effectively gauged by analyzing the resultant quantitative metrics or figures. Here are the numbers we deduced after migrating over 500 pages:

  • Page development time. Before Builder.io, it took 1–2 days to develop a page even if most of the components used were already built. But with Builder.io, it takes a maximum of 2–3 hours when most of the components are already built.
  • Time taken to churn out pages, including development, testing, and PR reviews. The time taken to churn out pages went down from 3–4 full pages on average to 14–15 full pages on average in two weeks, or one sprint.
  • Build time. The build time was reduced from more than 15 seconds to around 5 seconds as we extracted the codebase from the monolith and created a separate repository.
  • Rollout to production. Before Builder.io, the wait time for a rollout, which is the merging and building of a PR to the master branch, was more than an hour. Thanks to the new repo, the rollout time using Builder.io was reduced to 20–25 minutes!

With Builder.io, we were able to solve almost all the issues that the marketing pages faced using the previous tools. Here’s a summary of the most noteworthy improvements:

  • Simplified internationalization. It was quite easy to integrate our support for seven distinct languages to Builder.io with the user writing zero code. We introduced a simple prop for each of the languages that takes in values as a 0, 1, or 2. For any particular page, all the languages have this prop value set to 0, which means that translations aren’t yet triggered or required. After any language is set to 1, translations can be easily triggered from our Slack commands for all the languages that have this prop value set to 1. Once the translations are ready by our localization team, these prop values can be set to 2. After this, the translated text can be seen on the production page after publishing it. Figure 4 shows an example of how the Builder.io interface supports internationalization.
Figure 4. How the Builder.io interface supports internationalization.
  • Improved tracking. Users are only required to provide data in the form of props, which in turn is fed to our in-house tracking system.
  • Aligned A/B Testing. Support for A/B testing was added on the component and page levels.
  • Less dependence on the engineering team. With the introduction of Builder.io, marketing teams can create and manage pages and launch paid campaigns without being dependent on the engineering team. This has sped up the process to churn out pages.
  • Minimum outages. Our well thought and implemented architecture has reduced outages on these pages. These pages are easy to maintain, and any outages are quickly detected and resolved.

What we’re doing now

After migrating over 500 marketing pages using Builder.io, the marketing teams started a rebranding initiative in July 2023 to reconstruct the existing page components to align them with Vimeo’s new templates. In addition to rebuilding the old pages, they’ve also been diligently working on creating new ones. Leveraging Builder.io has significantly streamlined this extensive project, enabling the teams to launch approximately 20–25 pages in just about a month.

Take a look at some of our rebranded pages:

With gratitude

That was impressive, you reached the end of this blog! If you’re eager for further insights or have specific questions about any facet of the topics discussed, please don’t hesitate to drop your queries in the comments. I’m here and ready to delve deeper into any aspect you’d like to explore further.

I’d also like to extend a massive thank you to the amazing Vimeans, both past and present, who’ve been an integral part of this journey. A heartfelt appreciation goes out to Dayananda Nanjundappa for envisioning and initiating this project. Your attention to minute details has made it a wholesome solution to the problems we faced on certain pages. Thank you Tarzen Chugh and Pradeep Nayak for laying the foundation and constant guidance. Finally, but equally important, thank you Shubham Singh, Harshit Verma, and Shubham Rajput for your tireless efforts in ensuring the seamless integration of Builder.io into our workflows.

--

--