Time for a Website Upgrade


Adding theming and improving performance

17 June 2021

With the release of Nextjs 11, I decided it was time for some website improvements. Some of the key problems I wanted to solve was:

Nextjs to the Rescue

Thankfully, these first two are made easy by Nextjs 11. The recent placeholder property added to the next/image component allows for the easy generating of blur effects on the initial load of images. Check out more about the feature in their blog post.

Nextjs also added ESLint support with great defaults and performance-related linting. It's easy to get started by running npx next lint which configures ESLint and lets you know what you need to fix. This is included as a part of Conformance for Nextjs which looks like a great direction for the future of the framework.

Theming!

I originally wanted to just achieve a dark mode option for my website, but I decided I wanted to make more colour schemes avalible whilst still having an automatic colour switcher. My inspiration originally came from the Rebass website pictured below which has a similar colour scheme switcher.

Rebase Website

The styling of my website is done with TailwindCSS, and since it supports using CSS custom properties, that was where I started my implementation.

I used the strategy of creating a ThemeHandler to consume a theme from local storage, and a piece of global state provided by zustand.

export const useTheme = create<ThemeStore>((set, get) => ({
  theme:
    getLocalStorage("theme") ||
    (typeof window !== "undefined"
      ? window.matchMedia("(prefers-color-scheme: dark)").matches
        ? "dark"
        : "light"
      : "light"),
  updateTheme: (theme) => {
    setLocalStorage("theme", theme);
    set({ theme });
  },
  rotateTheme: () =>
    set((s) => {
      const keys = Object.keys(themes);
      const i = keys.indexOf(s.theme);
      const newTheme = (
        keys.length - 1 > i ? keys[i + 1] : keys[0]
      ) as keyof typeof themes;
      setLocalStorage("theme", newTheme);
      return { theme: newTheme };
    }),
}));

Because this is outside of a component and Nextjs code is run on the server, I do need to make sure that the window exists before checking if dark mode is active using window.matchMedia('(prefers-color-scheme: dark)').matches. Next, I needed to attach some CSS variables to the root of the document for the initial load of the website, and a set of themes to consume later.

:root {
  --brand: #d43c29;
  --on-brand: #ffffff;

  --accent: #6f5623;
  --accent-hover: #846929;
  --accent-focus: #9a7c2e;
  --on-accent: #ffffff;

  --background: #ffffff;
  --on-background: #4d5c63;
  --heading-on-background: #000;

  /* ... */
}
export const themes = {
  light: {
    brand: "#d43c29",
    /* ... */
  },
  dark: {
    brand: "#e36552",
    /* ... */
  },
};

Now we get to the fun stuff - actually changing the colour scheme. This is done in a useEffect hook so we can only change the theme only when we need to. By attaching an event listener for change to window.matchMedia('(prefers-color-scheme: dark)') we can update the theme automatically if the user turns on their computers dark mode.

We can then use our theme object to get the set of colours we need to apply and utilise /*...*/ style.setProperty(`--${key}`, value) to update the colour scheme. You can find below some more of the code I used for changing and handling the theme.

const theme = useTheme((s) => s.theme);
const updateTheme = useTheme((s) => s.updateTheme);

/* ... */

useEffect(() => {
  if (!document.documentElement) return;

  window
    .matchMedia("(prefers-color-scheme: dark)")
    .addEventListener("change", (e) => {
      if (e.matches) {
        updateTheme("dark");
      } else {
        updateTheme("light");
      }
    });

  const currentTheme = themes[theme];

  Object.keys(currentTheme).forEach((key) => {
    const value = currentTheme[key as keyof typeof currentTheme];
    document.documentElement.style.setProperty(`--${key}`, value);
  });
}, [theme, updateTheme]);

The Future

It was a lot of fun doing this performance refresh. Keep your eye out for more themes coming in the future. Some features I may look at adding next along these lines are a theme picker for code blocks and more development tooling to help create more performant images.

All Posts