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:
- Server-side storage - Using Next.js cookies to persist theme preferences
- Theme provider - A React context to make theme state available throughout the application
- 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:
- Gets the cookie store using Next.js's
cookies()
API - Sets a cookie named "current-theme" with the new theme value
- Calls
revalidatePath("/", "layout")
to revalidate the entire app's layout - 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:
- Maintains the current theme state using React's
useState
- 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
- Updates the DOM by setting a
- Offers a convenient
toggleMode
function to switch between dark and light themes - 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:
- The user clicks a theme toggle button, triggering the
toggleMode
orselectTheme
function - The function immediately updates the DOM by setting a
data-theme
attribute on the body element - 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
- The router is refreshed to apply any server-side changes
- 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!