Next.js Discord

Discord Forum

Dynamic theming

Answered
Oriental Scops-Owl posted this in #help-forum
Open in Discord
Oriental Scops-OwlOP
Hi, i have a SaaS application i want to allow the customer / app owner the ability to theme certain areas of the application.

what would be the best approach for something like this? i would be storing the theme values inside of my database and retrieving them via API call.

would it be best to fetch the theme palette from the API on the initial loading of the app, and store them in local storage or server-side cookie, then use a theme provider of some sort to load the values into CSS variables?

any link or guide to a tutorial how to do something similar would be appreciated. thanks
Answered by B33fb0n3
would it be best to fetch the theme palette from the API on the initial loading of the app
Yes

and store them in local storage or server-side cookie
No, why you want to, when they are saved in the cache or in css variables?

then use a theme provider of some sort to load the values into CSS variables?
Yes and of course use these css variables then
View full answer

17 Replies

Answer
Oriental Scops-OwlOP
thanks. i kind of figured out a solution without a theme provider. may still add one though.
How does your „no theme provider“ solution look like?
Oriental Scops-OwlOP
im using MUI theme provider in my main layout component:

    <AppRouterCacheProvider>
      <ThemeProvider theme={theme}>
        <GrubDialog
          open={locationDialogOpen}
          onClose={closeLocationDialog}
          title="Select a Location"
        >
          <LocationForm onSelect={handleUpdateLocation} location={currentLocation} />
        </GrubDialog>
        <>
          <Header onOpenLocations={toggleLocationDialog} properties={properties}/>
          <main className={styles.appContainer}>{children}</main>
          <Footer />
        </>
        <MobileNav onOpenLocations={toggleLocationDialog} />
      </ThemeProvider>
    </AppRouterCacheProvider>
i have this to fetch my tenant store properties and generate a theme object :

  const fetchProperties = async(): Promise<void> => {
    if (currentLocation) {
      setLoading(true)
      const resp = await fetch(`/api/locations/${currentLocation?.id!}/properties`)
      const json = await resp.json()
      setProperties(json.data)
      if (json.data.length > 0) {
        setTheme(generateTheme(json.data))
      }
      setLoading(false)
    }
  }
the fetchProperties is a useEffect on page load and whenever the store location changes
the only other thing i have left to make work is adjusting root CSS variables from within the container layout component
so when the app first loads in the browser, it fetches all of the properties for the selected store location and generates a theme object for MUI to use. it shows a loading text/headline until everything is loaded. seems to be working. its not the fastest solution, but i cant afford to build the customer app everytime he updates his settings. this is too costly with regards to resources and complicates everything
once the app loads, as you flip through router pages, everything is stored inside cache so its still pretty fast. its just the initial loading of the web app is a bit slow sometimes
Oriental Scops-OwlOP
so technically, i am using a theme provider, but it was the MUI provider.
Oriental Scops-OwlOP
thanks. im satisfied with the current solution but i will give it a look over
Oriental Scops-OwlOP
yep thats basically what i did
Happy to help 🙂
Hunting wasp
Hi, I have almost the same question and want to go into implementation details:

I want to use the app router. The basic MUI theme setup looks like this (based on https://mui.com/material-ui/integrations/nextjs/#app-router):
// layout.jsx
import theme from "@/theme"

export default function RootLayout({ children }) {
  return (
    <ThemeProvider theme={modifiedTheme}>
      ...
    </ThemeProvider>
  )
}


// theme.js
'use client';
import { createTheme } from "@mui/material/styles"

const theme = () => {
  const primaryColor = "#000000",

  return createTheme({
    palette: {
      primary: {
        main: primaryColor,
      },
    ...
  })
}


we're using a client-rendered theme, but put it into the still server-side <ThemeProvider> this way the children of <ThemeProvider> still stay in the server-side context.
Now I want to dynamically change the themes primary color to a value from my database. In my opinion this can and should still be possible server-side. However I didn't understand enough of the app router yet to figure out how to do that.

my approaches were to use a createCustomTheme({primaryColor}) function which returns createTheme() but I can't call it on server-side because it will be on the client. If I have a client component <CustomThemeProvider> it will force all of its children into client-rendering, which I think is not ideal...
Hunting wasp
well aparrently I'm overthinking this... https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props says it's okay to have server components as children of client components... in this case I will stick to my client side <CustomThemeProvider>