OS

Company
Development

How we built our public site with Next.js, Sanity and Emotion

Developing fast and reliably


Openlayer recently underwent a rebrand, and as part of our new identity, we released a new website, announced our funding, and opened up a sandbox edition of our product to the public.

While developing the new website and app, we found a few techniques that helped us build faster using Next.js + Vercel, Sanity, and Emotion. We are big fans of the Next.js and Vercel stack, and you can read more about our experience in our case study.

In this post, we want to share what we learned along the way, from design to implementation, performance to SEO.

Learn the secrets of performant and explainable ML

Be the first to know by subscribing to the blog. You will be notified whenever there is a new post.

Structuring the codebase

We use Next.js as a framework for both our website and app to rapidly create performant and scalable UI. Next.js comes with built-in support for all the common features needed for any full-stack application. It makes it simple and quick to handle everything from routing and data-fetching to performance and SEO optimizations.

One of the benefits of Next.js is its built-in filesystem-based routing. Every file or folder in the pages/ directory becomes a route on the server. For each page, you can optionally export a function getServerSideProps at the file level to fetch any data on the frontend server from your backend API (which, by the way, you can similarly create with Next by adding routes to pages/api/).

We decided, however, to omit any server-side rendering and instead fetch our data on the client after the request is fulfilled. This is recommended if server-side requests are not absolutely necessary in order to bring the initial page load down to near zero.

Another decision we made was to separate components from logic as much as possible. For example, we placed a lot of our logic into a separate lib/ directory. This has the benefit of readability as well as testability - these functions are decoupled and able to be easily unit-tested.

Leveraging key Next.js features

There are many optimizations that Next.js and Vercel provide that are worth mentioning, but we feel that the best features are image optimization, client-side navigation, and deployment previews.

Image optimization

Image optimization allows for static images to be optimized based on device size. Any images that we placed in the /public folder is rendered as multiple images of different sizes for mobile, tablet, and desktop screens, making content delivery faster for smaller devices.

For images that cannot be included in the /public folder (like images from a CMS), we can still optimize them for mobile screens using Sanity's image url queries.

<img src={urlFor(author.image).width(isMobile ? 120 : 200).url()} />

Client-side navigation

For any link between pages within our app, we can use the <Link> tag provided by next/link. Next.js will then transition between pages using only Javascript, without sending a new request. This allows for instant page transitions without losing the traditional page structure needed for search engine optimization (SEO).

And the results are dramatic. Since most websites on the web do not use this technique, the difference in speed is very noticeable, and it makes the site feel much leaner as a result. We get the performance of a single-page app (SPA) with the visibility of a traditional site.

Deployment previews

Vercel offers deployment previews on every PR pushed to your Github repo, even on the free tier. This feature is perfect for teams that need to collaborate on the design and implementation of a website before it goes live.

It allows teammates to post comments at specific pages and locations within the site. You can even tag each other or add images for context. As a dev tool, you can mark comments as resolved once they are finished, effectively serving as a shared to-do list for team feedback.

For even quicker feedback, Vercel also ships with the vercel deploy command which can launch a one-off deployment from the code in your current directory.

This allows developers to test out their changes on a mobile phone, for example, before pushing to the remote repo.

Tools like these are a superpower for collaborative feedback, dramatically reducing the time to fix bugs and ensuring a seamless user experience before launching a public site.

Integrating Sanity with Next.js

Our blog and change-log pull data from Sanity, which is a popular content management system (CMS) that comes with its own online editing tool. This allows writers to add posts to the site without touching any code.

With content management comes the quest of caching. Does each page visit send a separate request to the CMS, or can it be cached in the backend? Since the blog only updates each time we add or update a post, it does not make sense to re-render the entire blog index on each request.

Instead, Next.js provides something called incremental static regeneration (ISR), which lets us customize caching. By adding the “revalidate” property to getStaticProps, we can tell Next.js that we want our page to be "static,” meaning rendered before user requests, but also “incremental,” meaning updated every so often to keep it fresh. In our case, this update happens at most every 10 seconds. To a user visiting the page, there will be no additional backend delay due to the pre-rendering. But our blog will still reflect the latest content in Sanity (save for a small, acceptable delay).

Styling with Emotion.sh

As for styling, we use Emotion, which is a CSS-in-JS library for co-locating markup and styles. This is a natural tool when writing React code, since your layout is in Javascript (or Typescript, in our case). It also encourages modularity and separation of concerns, two tools to reduce the complexity of code.

In our case, we place a styles.ts file next to each component (e.g. NameCard.tsx). While developing, we can open the two files side-by-side to easily get a picture of all the relevant details for that component. Another win for readability and developer speed!

With Emotion, we try to use inheritance patterns as much as possible. We create several base components, from foundational flex or grid containers to more complex components that can be easily extended as needed.

One big benefit of using Emotion is the performance improvements when organizing our styles into modular pieces. Emotion will automatically cache reusable styled components into a separate class name (something like emotion-f14ac5). Anywhere that we reuse that style, its class is already stored by the browser for quick retrieval.

Of course, these hashes tell us nothing about where the style came from or what it is doing, so we use the autoLabel flag during development to append the folder and filename.

{ autoLabel: 'dev-only', labelFormat: '[dirname]-[local]' }

This makes visual debugging from the browser’s web inspector much faster.

Typed CSS

Another benefit of using Emotion with Typescript is that it gives us Intellisense on CSS properties.

When using styled components (as opposed to CSS strings), each style block is a Javascript object, so the keys and values can be statically typed. This speeds up development because we get feedback on typos from within the editor, before we render in the browser. Small improvements like this add up over time.

Theming

Emotion also comes with builtin theme support, which means that all styled components can take an implicit theme property without having to explicitly pass it through from a parent component, like this:

export const Paragraph = styled.p(({ theme }) => ({
  color: theme.color.positive,
}));

To enable themes, we just need to wrap our app in a ThemeProvider:

<ThemeProvider theme={currentTheme}>{page}</ThemeProvider>

Since our mobile site had a lot of common markup with the desktop version, it made sense to also include an "isMobile" property to our theme. We could simply update the Theme type like so:

const currentTheme = {
  isMobile,
  color,
}

Now, all our styles have access to this information, without having to pass an additional prop throughout the tree:

export const Paragraph = styled.p(({ theme }) => ({
  color: theme.color.positive,
  fontSize: theme.isMobile ? 12 : 16,
}));

Conclusion

Whether you are creating a new website from scratch or reworking a legacy site, you want to be able to build fast. It is natural to leave optimization for a later time or to skip some industry best practices. But knowing about the best tools and techniques beforehand can make development faster in the long run. There are many small decisions that add up to huge savings when it comes to getting fast feedback or reducing the cost of maintenance.

Using popular tools like Next.js + Vercel, Sanity, and Emotion are a great way to bring in some of these best practices from the start, since they are all developed by web experts with many years of experience in the field. We also highly recommend Zustand, react-query, and zod for state and network query management, but more on that for a later post about the stack behind our core product.

All of these tools are essentially free to start using now, so give them a whirl if you aren’t already! If you want to see some more industry-leading tools in action, the Next.js Enterprise Boilerplate repo is a great place to start.

We hope you found this post useful in your own development. Feel free to reach out to us on social media with any comments or feedback. We’d love to hear what you think!

Recommended posts

Company

Navigating the chaos: why you don’t need another MLOps tool

And how to build trustworthy AI

Openlayer

November 29th, 2023 • 5 minute read

Navigating the chaos: why you don’t need another MLOps tool
Company

Openlayer raises $4.8m seed round to build guardrails for AI

Read about our financing round led by Quiet Capital

Openlayer

May 4th, 2023 • 4 minute read

Openlayer raises $4.8m seed round to build guardrails for AI
Company
Explainability
Error analysis
Data-centric

The race to put AI to work

A tipping point or hype for businesses and environmental, social, and corporate governance (ESG)?

Vikas Nair

June 28th, 2022 • 10 minute read

The race to put AI to work