Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The way avoid hydration errors has a negative impact on SEO. #289

Closed
minjunkyeong opened this issue Apr 29, 2024 · 2 comments
Closed

The way avoid hydration errors has a negative impact on SEO. #289

minjunkyeong opened this issue Apr 29, 2024 · 2 comments

Comments

@minjunkyeong
Copy link

To avoid hydration errors, the example returns null until it is mounted, and this is not an appropriate approach for seo.

Avoid Hydration Mismatch

import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'

const ThemeSwitch = () => {
  const [mounted, setMounted] = useState(false)
  const { theme, setTheme } = useTheme()

  // useEffect only runs on the client, so now we can safely show the UI
  useEffect(() => {
    setMounted(true)
  }, [])

  if (!mounted) {
    return null
  }

  return (
    <select value={theme} onChange={e => setTheme(e.target.value)}>
      <option value="system">System</option>
      <option value="dark">Dark</option>
      <option value="light">Light</option>
    </select>
  )
}

export default ThemeSwitch

So, if I have to worry about seo, isn't it appropriate to use this library that uses local storage?

@minjunkyeong minjunkyeong changed the title The way avoid hydration errors has a negative impact on your SEO. The way avoid hydration errors has a negative impact on SEO. Apr 30, 2024
@minjunkyeong
Copy link
Author

minjunkyeong commented Apr 30, 2024

In the end, I implemented theme functionality based on cookies... On every page getServersideProps, 'theme', 'themeSource' cookies must be parsed and passed to DarkModeProvider which used in _app.tsx

// context/DarkModeContext.tsx

import {
  ReactNode,
  createContext,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from 'react';
import { setCookie } from 'cookies-next';
import { useMediaQuery } from '@mui/material';

type DarkModeContextType = ReturnType<typeof useDarkMode>;

const DarkModeContext = createContext<DarkModeContextType | undefined>(
  undefined,
);

// theme source name
const SYSTEM = 'system' as const;
const CUSTOM = 'custom' as const;

// theme
const LIGHT = 'light' as const;
const DARK = 'dark' as const;
const DEFAULT_THEME = LIGHT;

//cookie name
const THEME = 'theme' as const;
const THEME_SOURCE = 'themeSource' as const;


const useDarkMode = ({
  cookieTheme,
  cookieThemeSource,
}: {
  cookieThemeSource: typeof SYSTEM | typeof CUSTOM | null;
  cookieTheme: typeof LIGHT | typeof DARK | null;
}) => {
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
  const prefersLightMode = useMediaQuery('(prefers-color-scheme: light)');

  const [theme, setTheme] = useState<typeof LIGHT | typeof DARK>(
    cookieTheme || DEFAULT_THEME,
  );
  const [source, setSource] = useState<typeof SYSTEM | typeof CUSTOM>(
    cookieThemeSource || CUSTOM,
  );

  const darkModeActive = useMemo(() => theme === DARK, [theme]);
  const autoModeActive = useMemo(() => source === SYSTEM, [source]);

  const setValues = useCallback(
    ({
      selectedTheme,
      selectedSource,
    }: {
      selectedTheme: typeof LIGHT | typeof DARK;
      selectedSource: typeof SYSTEM | typeof CUSTOM;
    }) => {
      setCookie(THEME, selectedTheme);
      setCookie(THEME_SOURCE, selectedSource);

      setTheme(selectedTheme);
      setSource(selectedSource);
    },
    [],
  );

  const switchToDarkMode = useCallback(() => {
    setValues({ selectedTheme: DARK, selectedSource: CUSTOM });
  }, [setValues]);
  const switchToLightMode = useCallback(() => {
    setValues({ selectedTheme: LIGHT, selectedSource: CUSTOM });
  }, [setValues]);
  const switchToAutoMode = useCallback(() => {
    setValues({
      selectedTheme: prefersDarkMode ? DARK : LIGHT,
      selectedSource: SYSTEM,
    });
    setTheme(prefersDarkMode ? DARK : LIGHT);
  }, [prefersDarkMode, setValues]);

  // If there is no value saved in cookies when accessing the website for the first time, set the theme according to the user system settings.
  useEffect(() => {
    if (cookieThemeSource === null && cookieTheme === null) {
      switchToAutoMode();
    }
  }, [cookieTheme, cookieThemeSource, switchToAutoMode]);

  // Change theme when changing system theme settings
  useEffect(() => {
    if (
      source === SYSTEM &&
      (prefersDarkMode === true || prefersLightMode === true)
    ) {
      switchToAutoMode();
    }
  }, [prefersDarkMode, prefersLightMode, source, switchToAutoMode]);

  return {
    autoModeActive,
    darkModeActive,
    switchToAutoMode,
    switchToDarkMode,
    switchToLightMode,
    theme,
    cookieThemeSource,
  };
};

const DarkModeProvider = ({
  children,
  cookieTheme,
  cookieThemeSource,
}: {
  cookieThemeSource: typeof SYSTEM | typeof CUSTOM | null;
  cookieTheme: typeof LIGHT | typeof DARK | null;
  children: ReactNode;
}) => {
  const darkModeState = useDarkMode({
    cookieTheme,
    cookieThemeSource,
  });

  return (
    <DarkModeContext.Provider value={darkModeState}>
      {children}
    </DarkModeContext.Provider>
  );
};

export { DarkModeProvider, DarkModeContext };

@trm217
Copy link
Collaborator

trm217 commented May 2, 2024

Simply put, if we use local-storage, there is no way for the current theme to be rendered, before loading the theme from the local-storage on the client.

Storing the state in a cookie would work, but as of this library does not support other storage solutions other than local-storage as of now.

If we were to allow custom storage solutions (which he have already discussed) this could possible be handled by the library, but as of now, this is out of scope.

@trm217 trm217 closed this as completed May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants