Efficient Theme Management in Next.js with Server Actions

A deep dive into implementing a robust theme management system using Next.js server actions, cookies, and React context
March 26, 2025

The Challenge of Theme Management

Modern web applications need to support multiple themes - typically light and dark modes - while ensuring a seamless user experience. An effective theme system should:

  • Persist user preferences across sessions
  • Apply theme changes instantly without page reloads
  • Work with server-side rendering
  • Minimize layout shifts during theme transitions
  • Be easily extensible for additional themes

In this post, I'll explain how we implemented our theme management system using Next.js server actions, cookies for persistence, and React context for state management.

The Architecture

Our theme system consists of three main components:

  1. Server-side storage - Using Next.js cookies to persist theme preferences
  2. Theme provider - A React context to make theme state available throughout the application
  3. Theme switcher components - UI elements that allow users to change themes

Let's look at each component in detail.

Server-Side Theme Storage

The foundation of our theme system is a set of server actions that handle theme persistence. Here's how we implement it:

Loading...

The key function here is setCurrentTheme, which:

  1. Gets the cookie store using Next.js's cookies() API
  2. Sets a cookie named "current-theme" with the new theme value
  3. Calls revalidatePath("/", "layout") to revalidate the entire app's layout
  4. Returns the new theme value

This server action approach leverages Next.js's built-in capabilities to handle cookies securely on the server side.

Theme Provider Context

To make the theme available throughout our application, we use a React context provider:

Loading...

The theme provider does several important things:

  1. Maintains the current theme state using React's useState
  2. Provides a selectTheme function that:
    • Updates the DOM by setting a data-theme attribute on the body
    • Calls our server action to persist the theme in cookies
    • Refreshes the router to apply server-side changes
    • Updates the local state
  3. Offers a convenient toggleMode function to switch between dark and light themes
  4. Exposes these functions through a custom useTheme hook

Applying Themes in the UI

With our theme provider in place, we can easily create UI components to change themes:

Loading...

The Complete Flow

When a user changes their theme preference, here's what happens:

  1. The user clicks a theme toggle button, triggering the toggleMode or selectTheme function
  2. The function immediately updates the DOM by setting a data-theme attribute on the body element
  3. The server action setCurrentTheme is called, which:
    • Stores the new theme preference in a cookie
    • Revalidates the app's layout to ensure server components reflect the change
  4. The router is refreshed to apply any server-side changes
  5. The local state is updated, causing any components that depend on the theme to re-render

This approach gives us several benefits:

  • Immediate visual feedback - The theme changes instantly by updating the DOM directly
  • Persistence across sessions - Theme preferences are stored in cookies
  • Server-side rendering support - The theme is available during SSR through cookies
  • No layout shifts - By updating the DOM before server revalidation, we avoid layout shifts

CSS Implementation

To make this work with CSS, we use CSS variables with different values based on the data-theme attribute:

Loading...

Conclusion

This theme management system provides a robust solution for Next.js applications. By combining server actions, cookies, and React context, we've created a system that:

  • Persists user preferences
  • Works seamlessly with SSR
  • Provides instant visual feedback
  • Avoids layout shifts
  • Is easily extensible

The use of Next.js server actions makes this approach particularly elegant, as it leverages the framework's built-in capabilities for handling server-side state while maintaining a clean separation of concerns.

Feel free to adapt this approach for your own projects, and let me know if you have any questions or suggestions for improvement!