Notes

How to Implement Theming in SSR. (Nuxt 3 + Tailwind)

Last updated: 3rd October, 2024   Author: Ugochukwu Okeke

Typically in CSR (client-side rendered) websites, working with tailwind, your theme setup would probably look something like this:


useTheme.js

function registerTheme() {
   const theme = localStorage.getItem(THEME_STORAGE_KEY);

   // check if it's user's first visit
   if (theme === null) {

      // set theme according to user's device preference
      if ( 
         window.matchMedia && 
         window.matchMedia("(prefers-color-scheme: dark)").matches
      ) {
         document.querySelector("html").classList.add("dark");
         localStorage.setItem(THEME_STORAGE_KEY, "dark")
      } 
      else {
         document.querySelector("html").classList.remove("dark");
         localStorage.setItem(THEME_STORAGE_KEY, "light")
      }
   } 
   
   // else use saved theme
   else {
      document.querySelector("html").classList.add(theme);
   }

}

export { registerTheme }

Then in your vue component you call your register function, while Tailwind handles the rest.


App.vue

<script setup>
   import { registerTheme } from 'FILE_PATH'

   onMounted(() => {
      registerTheme()
   })
</script>

<template>
   <div class="bg-white dark:bg-black">
      <h1> Stay Cavy </h1>
   </div>
</template>


This usually works fine for client-rendered websites. But it doesn't really suffice for SSR (server-side rendered) websites . Why?


As opposed to client-side rendering, SSR websites are rendered twice — once on the server, and again on the client during hydration. So the default theme of your website is rendered on the server first, before the proper theme is then registered on the client side.


So for users that their preferred or set theme is not your website's default, every time they visit your website, the default theme will be seen first in the browser (as rendered on the server), for a split sec or more, depending on the size of your website or app (even more if it's heavily animated), before their preferred theme is set.


Just as it sounds, it doesn't make for really good UX.


If you try to manipulate the DOM and force a quick change, it might work, but you'd most like end up with a Hydration Mismatch Error or Warning


So how do you prerender your website with the right theme?
Easy, Cookies!


Now Nuxt.js even makes this easier, and more beautiful with the useCookie [docs here] auto imported composable. It's an SSR friendly way of setting and getting cookies, and it's reactive too. Hehe


So we refactor.


Just so you understand where we're going — as with every sever-client comms, for every request made to the server from the client, if there are any registered cookies from the server, they're automatically added to the request being sent.


Nuxt.js (SSR mode) is same. For every route (page), a request is sent to the server. The request is read and the corresponding resource is sent back to the client.


Now we just have to send our preferred theme along, so our page is prerendered with the correct theme on the server. Hence:


useTheme.js

function registerTheme() {
   const theme = useCookie(THEME_STORAGE_KEY);

   // check if it's user's first visit 
   if (!theme.value) {

      // set theme according to user's device preference
      window.matchMedia("(prefers-color-scheme: dark)").matches
         ?  SET_THEME('dark')
         :  SET_THEME('light')

   } 
}

Then in your vue component:


App.vue

<script setup>
   const themeCookie = useCookie(THEME_STORAGE_KEY)

   onMounted(() => {
      registerTheme()
   })
</script>

<template>
   <div 
      class="layout__wrapper"
      :class="{'dark': themeCookie === 'dark'}"
   >
      <NuxtLayout class="bg-white dark:bg-black">
         <NuxtPage />
      </NuxtLayout>
   </div>
</template>


In this case, our default theme is 'light', so when a page is requested, server-side nuxt checks if a theme cookie was sent along. A class is then conditional bound to our topmost element depending on the cookie's value.


If the value is 'dark', the 'dark' class is added to element and dark mode is rendered. If it is not the default theme is rendered and sent back to the client.


With this, the correct theme will always be rendered. ( Except of course on first visit, as no cookies from the server would exist yet, and that's okay. Happens even in CSR ).




So there you have it!


This is my first article ever so I hope it was explanatory enough. I look forward to improving and writing more, so I'd appreciate any feedback.


You can reach me on twitter @thecavydev.


Till next time.

More from ColoSach

Related articles, news and information.
how-to-implement-theming-in-ssr-nuxt-tailwind
Typically in CSR (client-side rendered) websites, working with tailwind, your theme setup would probably look something like this. This usually works fine for client-rendered websites. But it doesn't really suffice for SSR (server-side rendered) websites . Why?
sample-blog-title-placeholder-2
This is a sample blog description. It should exclude headings and can span up to four lines of text. This is a sample blog description. It should exclude headings and can span up to four lines of text. This is a sample blog description. It should exclude headings and can span up to four lines of text. span three lines of text.
sample-blog-title-placeholder-3
This is a sample blog description. It should exclude headings and can span up to four lines of text. This is a sample blog description. It should exclude headings and can span up to four lines of text. This is a sample blog description. It should exclude headings and can span up to four lines of text. span three lines of text.
Toast mssg