1. Co zepsuło się w React 19
  2. Bezpośredni zamiennik
  3. Co dodaje
  4. Cookie storage bez mignięcia
  5. Typowane motywy
  6. Zagnieżdżone providery
  7. ThemedImage i useThemeValue
  8. Dostęp po stronie serwera
  9. Od tamtej pory
  10. Dokumentacja
  1. Co zepsuło się w React 19
  2. Bezpośredni zamiennik
  3. Co dodaje
  4. Cookie storage bez mignięcia
  5. Typowane motywy
  6. Zagnieżdżone providery
  7. ThemedImage i useThemeValue
  8. Dostęp po stronie serwera
  9. Od tamtej pory
  10. Dokumentacja
Co zepsuło się w React 19
Blog

@wrksz/themes: dlaczego przepisałem next-themes od zera

next-themes notuje 22 miliony pobrań tygodniowo i od ponad roku nie dostał nowej wersji. Napisałem bezpośredni zamiennik, który naprawia błędy, dodaje cookie SSR i typowane motywy.

Opublikowano
30 marca 2026
Zaktualizowano
24 września 2026
Czas czytania
4 min
Tagi
next.jsreacttypescriptopen source

W marcu 2026 next-themes od roku nie miał nowego wydania (0.4.6, marzec 2025). Liczby z tamtego momentu:

pobrań tygodniowo
22 mln
otwarte zgłoszenia
44
pull requestów czekających na review
17

Nikt niczego nie mergował, a na nowszych wersjach Reacta i Next.js biblioteka zaczęła się sypać.

Co zepsuło się w React 19#

Podczas migracji Hostero do Next.js 16 i React 19 next-themes zaczął wypisywać w konsoli:

Encountered a script tag while rendering React component.
Scripts inside React components are never executed when rendering on the client.

Otworzyłem pull requesta, sprawdziłem aktywność w repozytorium i uznałem, że porządne przepisanie 300-linijkowej biblioteki ma więcej sensu niż utrzymywanie forka. Ostrzeżenie okazało się zresztą jednym z czterech problemów:

ObjawPrzyczyna w next-themesPoprawka w @wrksz/themes
ostrzeżenie o <script> przy każdym renderzeblokujący skrypt renderuje się w Client Componentwstrzykiwany przez useServerInsertedHTML, poza drzewem komponentów
motyw utyka na starej wartości przy włączonym cacheComponentsstan trzymany w useStateosobny store dla każdego providera, czytany przez useSyncExternalStore
ReferenceError: __name is not defined w części buildów produkcyjnychskrypt powstaje przez Function.toString(), więc helper __name z bundlera trafia do kodu, który działa bez niegoskrypt trafia do paczki jako gotowy string
InvalidCharacterError przy value={{ dark: "dark high-contrast" }}cała wartość trafia do classList jako jeden tokenklasy dzielone przez flatMap przed dodaniem i usunięciem

Bezpośredni zamiennik#

@wrksz/themes zachowuje te same propsy i hooki, więc przejście to głównie instalacja i nowe importy:

@wrksz/themesA modern, fully-featured theme management library for Next.js122 gwiazdki47,5 tys. pobrania w tym tygodniuv2.0.2 najnowsza
npm install @wrksz/themes
npm uninstall next-themes
pnpm add @wrksz/themes
pnpm remove next-themes
bun add @wrksz/themes
bun remove next-themes
import { ThemeProvider } from "next-themes"; 
import { useTheme } from "next-themes"; 
import { ThemeProvider } from "@wrksz/themes/next"; 
import { useTheme } from "@wrksz/themes/client"; 

Provider importujesz w layoucie, czyli w Server Component, a hooki w Client Components. Różni się jedna wartość domyślna: next-themes ustawia atrybut data-theme, a @wrksz/themes klasę. Jeśli twój CSS opiera się na data-theme, dodaj attribute="data-theme".

Co dodaje#

Cookie storage bez mignięcia#

Przy storage="cookie" motyw zapisuje się w cookie, a skrypt providera odczytuje je, zanim przeglądarka narysuje pierwszą klatkę, więc strona nie mignie złym motywem. Serwer w ogóle nie czyta cookie, więc layout może zostać statyczny:

<ThemeProvider storage="cookie" defaultTheme="dark">
  {children}
</ThemeProvider>

Typowane motywy#

Podajesz własny typ z listą motywów, a setTheme odrzuci każdą inną wartość już przy kompilacji:

import { useTheme } from "@wrksz/themes/client";

type AppTheme = "light" | "dark" | "high-contrast";

const { theme, setTheme } = useTheme<AppTheme>();
setTheme("sepia");
Argument of type '"sepia"' is not assignable to parameter of type '((current: ThemeSelection<AppTheme> | undefined) => ThemeSelection<AppTheme>) | ThemeSelection<AppTheme>'.

Zagnieżdżone providery#

Każdy provider ma własny store, więc dwie części jednej strony mogą jednocześnie działać na różnych motywach, o ile każda dostanie własny target i storageKey. Przydaje się to w bibliotekach komponentów, embedach i izolowanych podglądach.

ThemedImage i useThemeValue#

ThemedImage wybiera obrazek dla bieżącego motywu bez hydration mismatch:

import { ThemedImage } from "@wrksz/themes/client";

<ThemedImage
    src={{ light: "/logo-light.png", dark: "/logo-dark.png" }}
    alt="Logo"
/>

useThemeValue robi to samo dla dowolnej wartości:

import { useThemeValue } from "@wrksz/themes/client";

const label = useThemeValue({
    light: "Switch to dark",
    dark: "Switch to light",
});

Dostęp po stronie serwera#

getTheme() czyta cookie z motywem bez wciągania Reacta. W proxy albo middleware przekazujesz request:

import { NextResponse } from "next/server";
import { getTheme } from "@wrksz/themes/next";

export function proxy(request: Request) {
  const theme = getTheme(request, { defaultTheme: "dark" });
  const response = NextResponse.next();
  response.headers.set("x-theme", theme);
  return response;
}

W Server Component, layoucie albo server action wywołujesz ją bez requestu, z await: await getTheme({ defaultTheme: "dark" }). Odczyt cookie sprawia, że strona renderuje się przy każdym żądaniu, zamiast być statyczna. Wynikiem może też być "system", więc zamień go na konkretny motyw, zanim użyjesz go jako klasy.

Mniejsze dodatki
  • obsługa sessionStorage
  • storage: "none" dla w pełni kontrolowanych motywów
  • meta theme-color dla Safari i PWA
  • storage hybrid: cookie plus synchronizacja między kartami przez localStorage

Od tamtej pory#

Post powstał przy wersji 0.7.9. Tak projekt doszedł do obecnego stanu:

  1. 20 mar 2026
    20 mar 2026

    Pull request do next-themes

    Poprawka ostrzeżenia o skrypcie w React 19. Nadal otwarty.
  2. 21 mar 2026
    21 mar 2026

    0.1.0 na npm

    Osiem wydań pierwszego dnia, aż do 0.5.0.
  3. 30 mar 2026
    30 mar 2026

    Ten post

    Napisany przy wersji 0.7.9.
  4. 23 kwi 2026
    23 kwi 2026

    0.9.0

    Storage hybrid, fabryka createThemes i useThemeEffect.
  5. 7 lip 2026
    7 lip 2026

    1.0.0

    Paczka na npm mniejsza o 46% po rozdzieleniu deklaracji typów.
  6. 3 sie 2026
    3 sie 2026

    1.1.0

    Pierwszy zewnętrzny kontrybutor, @martijn00, z przenośnym API dla SSR i klienta.
  7. 20 wrz 2026
    20 wrz 2026

    2.0.0

    Koniec niejawnego czytania cookie na serwerze pod Next.js 16.3 i wymagany TypeScript 5.9.

Dokumentacja#

Pełne API jest na themes.wrksz.dev. Przy przejściu z next-themes zacznij od przewodnika migracji.

Nowe posty i notatki mailem.

StarszySztuczna inteligencja nie zastąpi programistów
© 2026·6a5688e
Blog