> 웹 프론트엔드 > JS 튜토리얼 > SSR React 애플리케이션에서 테마 설정

SSR React 애플리케이션에서 테마 설정

Patricia Arquette
풀어 주다: 2025-01-06 00:56:38
원래의
605명이 탐색했습니다.

Setting Up Themes in SSR React Applications

밝은 테마, 어두운 테마, 시스템 기반 테마를 손쉽게 전환하면서 귀하의 선호도에 맞춰 원활하게 조정되는 웹사이트를 방문한다고 상상해 보십시오.

이 기사에서는 React를 사용한 SSR에 대한 시리즈를 이어갑니다. 기본 기사에서는 생산 준비가 완료된 구성을 살펴봤고, 고급 기술에서는 수화 오류와 같은 문제를 해결했습니다. 이제 SSR과 원활하게 통합되는 강력한 테마 지원을 구현하여 한 단계 더 발전하겠습니다.

목차

  • 테마와 SSR
  • 구현
    • 종속성 설치
    • 서버 빌드에 쿠키 추가
    • 서버에 테마 적용
    • 클라이언트에서 테마 처리
  • 결론

테마 및 SSR

주요 이슈는 잘못된 테마의 초기 플래시(FOIT)입니다.

기본적으로 테마는 CSS 변수를 변경하는 것입니다. 대부분의 경우 세 가지 테마로 작업하게 됩니다.

  • Light: CSS 변수의 기본 세트
  • 어둡게: 태그의 클래스는 dark입니다.
  • 시스템: (선호-색상-구성: 어두움)을 사용하여 사용자의 시스템 기본 설정에 따라 자동으로 전환합니다. 테마가 어둡거나 밝아야 하는지 결정하기 위한 미디어 쿼리입니다.

기본적으로 서버는 밝은 테마로 HTML을 렌더링하여 브라우저로 보냅니다. 사용자가 어두운 테마를 선호하는 경우 첫 번째 페이지 로드 시 시각적인 테마 변경이 표시되어 사용자 경험을 방해합니다.

이 문제를 해결하는 두 가지 주요 방법은 다음과 같습니다.

  • <스크립트> 추가 서버의 HTML에 태그를 지정하고 클라이언트에서 클래스를 동적으로 설정합니다.
  • 쿠키를 사용하여 사용자의 테마 기본 설정을 저장하고 서버에 클래스를 설정합니다.

첫 번째 해결 방법은 다음 테마 패키지의 작동 방식입니다(2025년 1월). 이 기사에서는 SSR 애플리케이션에서 원활한 테마 처리를 보장하기 위해 쿠키 기반 접근 방식을 구현합니다.

구현

테마를 구현하려면 다음 두 가지 쿠키를 사용합니다.

  1. serverTheme - 태그.
  2. clientTheme - 수화 오류를 처리하는 데 사용됩니다.

클라이언트는 항상 두 쿠키를 모두 설정하여 서버가 다음 요청 시 적절한 테마를 올바르게 렌더링할 수 있도록 합니다.

이 가이드는 이전 기사인 생산 가능한 SSR React 애플리케이션 구축에서 소개된 개념을 기반으로 하며, 해당 기사는 하단에 링크되어 있습니다. 단순화를 위해 공유 상수 및 유형은 여기에서 생성되지 않지만 예제 저장소에서 해당 구현을 찾을 수 있습니다.

종속성 설치

쿠키 처리에 필요한 패키지를 설치합니다.

pnpm add cookie js-cookie
로그인 후 복사

js-cookie 설치 유형:

pnpm add -D @types/js-cookie
로그인 후 복사

앱에서 반응 라우터를 사용하지 않는 경우 쿠키 패키지를 devDependency로 사용할 수 있습니다.

서버 빌드에 쿠키 추가

tsup 구성 파일 업데이트:

// ./tsup.config.ts
import { defineConfig } from 'tsup'

export default defineConfig({
  entry: ['server'],
  outDir: 'dist/server',
  target: 'node22',
  format: ['cjs'],
  clean: true,
  minify: true,
  external: ['lightningcss', 'esbuild', 'vite'],
  noExternal: [
    'express',
    'sirv',
    'compression',
    'cookie', // Include the cookie in the server build
  ],
})
로그인 후 복사

서버에 테마 적용

테마 상수 정의

// ./server/constants.ts
export const CLIENT_THEME_COOKIE_KEY = 'clientTheme'
export const SERVER_THEME_COOKIE_KEY = 'serverTheme'

export enum Theme {
  Light = 'light',
  Dark = 'dark',
  System = 'system'
}
로그인 후 복사

태그에 테마 클래스 적용

올바른 테마 클래스를 serverTheme 쿠키를 기반으로 하는 태그:

// ./server/lib/applyServerTheme.ts
import { parse } from 'cookie'
import { Request } from 'express'
import { SERVER_THEME_COOKIE_KEY, Theme } from '../constants'

export function applyServerTheme(req: Request, html: string): string {
  const cookies = parse(req.headers.cookie || '')
  const theme = cookies?.[SERVER_THEME_COOKIE_KEY]

  if (theme === Theme.Dark) {
    return html.replace('<html lang="en">', `<html lang="en">



<h4>
  
  
  Retrieve the Client Theme Cookie
</h4>

<p>Create a utility function to retrieve the clientTheme cookie<br>
</p>

<pre class="brush:php;toolbar:false">// ./server/getClientTheme.ts
import { parse } from 'cookie'
import { Request } from 'express'
import { CLIENT_THEME_COOKIE_KEY, Theme } from '../constants'

export function getClientTheme(req: Request) {
  const cookies = parse(req.headers.cookie || '')

  return cookies?.[CLIENT_THEME_COOKIE_KEY] as Theme | undefined
}
로그인 후 복사

테마를 위한 서버 구성 업데이트

개발 구성:

// ./server/dev.ts
import fs from 'fs'
import path from 'path'
import { Application } from 'express'
import { HTML_KEY } from './constants'
import { applyServerTheme } from './lib/applyServerTheme'
import { getClientTheme } from './lib/getClientTheme'

const HTML_PATH = path.resolve(process.cwd(), 'index.html')
const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx')

export async function setupDev(app: Application) {
  const vite = await (
    await import('vite')
  ).createServer({
    root: process.cwd(),
    server: { middlewareMode: true },
    appType: 'custom',
  })

  app.use(vite.middlewares)

  app.get('*', async (req, res, next) => {
    try {
      let html = fs.readFileSync(HTML_PATH, 'utf-8')
      html = await vite.transformIndexHtml(req.originalUrl, html)

      const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH)
      // send Client Theme from cookie to render
      const appHtml = await render(getClientTheme(req))

      // Apply Server theme on template html
      html = applyServerTheme(req, html)
      html = html.replace(HTML_KEY, appHtml)

      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      vite.ssrFixStacktrace(e as Error)
      console.error((e as Error).stack)
      next(e)
    }
  })
}
로그인 후 복사

제작 구성:

// ./server/prod.ts
import fs from 'fs'
import path from 'path'
import compression from 'compression'
import { Application } from 'express'
import sirv from 'sirv'
import { HTML_KEY } from './constants'
import { applyServerTheme } from './lib/applyServerTheme'
import { getClientTheme } from './lib/getClientTheme'

const CLIENT_PATH = path.resolve(process.cwd(), 'dist/client')
const HTML_PATH = path.resolve(process.cwd(), 'dist/client/index.html')
const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'dist/ssr/entry-server.js')

export async function setupProd(app: Application) {
  app.use(compression())
  app.use(sirv(CLIENT_PATH, { extensions: [] }))

  app.get('*', async (req, res, next) => {
    try {
      let html = fs.readFileSync(HTML_PATH, 'utf-8')

      const { render } = await import(ENTRY_SERVER_PATH)
      // send Client Theme from cookie to render
      const appHtml = await render(getClientTheme(req))

      // Apply Server theme on template html
      html = applyServerTheme(req, html)
      html = html.replace(HTML_KEY, appHtml)

      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      console.error((e as Error).stack)
      next(e)
    }
  })
}
로그인 후 복사

클라이언트에서 테마 처리

상수 정의

클라이언트용으로 상수를 복제하거나 공유 폴더로 이동

// ./src/constants.ts
export const SSR = import.meta.env.SSR

export const CLIENT_THEME_COOKIE_KEY = 'clientTheme'
export const SERVER_THEME_COOKIE_KEY = 'serverTheme'

export enum Theme {
  Light = 'light',
  Dark = 'dark',
  System = 'system',
}
로그인 후 복사

테마 컨텍스트 생성

테마 상태를 관리하고 테마 관리 방법을 제공하기 위해 React 컨텍스트를 설정합니다.

// ./src/theme/context.ts
import { createContext, useContext } from 'react'
import { Theme } from '../constants'

export type ThemeContextState = {
  theme: Theme
  setTheme: (theme: Theme) => void
}

export const ThemeContext = createContext<ThemeContextState>({
  theme: Theme.System,
  setTheme: () => null,
})

export const useThemeContext = () => useContext(ThemeContext)
로그인 후 복사

테마 유틸리티 구현

// ./src/theme/lib.ts
import Cookies from 'js-cookie'
import { CLIENT_THEME_COOKIE_KEY, SERVER_THEME_COOKIE_KEY, SSR, Theme } from '../constants'

// Resolve the system theme using the `prefers-color-scheme` media query
export function resolveSystemTheme() {
  if (SSR) return Theme.Light
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.Dark : Theme.Light
}

// Update the theme cookies and set appropriate class to <html>
export function updateTheme(theme: Theme) {
  if (SSR) return

  const resolvedTheme = theme === Theme.System ? resolveSystemTheme() : theme

  Cookies.set(CLIENT_THEME_COOKIE_KEY, theme)
  Cookies.set(SERVER_THEME_COOKIE_KEY, resolvedTheme)

  window.document.documentElement.classList.toggle('dark', resolvedTheme === Theme.Dark)
}

// Get the default theme from cookies
export function getDefaultTheme(): Theme {
  if (SSR) return Theme.System
  const theme = (Cookies.get(CLIENT_THEME_COOKIE_KEY) as Theme) || Theme.System

  updateTheme(theme)
  return theme
}
로그인 후 복사

테마 공급자 생성

// ./src/theme/Provider.tsx
import { PropsWithChildren, useState } from 'react'
import { Theme } from '../constants'
import { ThemeContext } from './context'
import { getDefaultTheme, updateTheme } from './lib'

type Props = PropsWithChildren & {
  defaultTheme?: Theme // Handle theme for SSR
}

export function ThemeProvider({ children, defaultTheme }: Props) {
  const [theme, setTheme] = useState<Theme>(defaultTheme || getDefaultTheme())

  const handleSetTheme = (theme: Theme) => {
    setTheme(theme)
    updateTheme(theme)
  }

  return <ThemeContext value={{ theme, setTheme: handleSetTheme }}>{children}</ThemeContext>
}
로그인 후 복사
// ./src/theme/index.ts
export { ThemeProvider } from './Provider'
export { useThemeContext } from './context'
로그인 후 복사

구성 요소에서 테마 컨텍스트 사용

// ./src/App.tsx
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import Card from './Card'
import { Theme } from './constants'
import { ThemeProvider } from './theme'

import './App.css'

// Theme from Server Entry
type AppProps = {
  theme?: Theme
}

function App({ theme }: AppProps) {
  return (
    <ThemeProvider defaultTheme={theme}>
      <div>
        <a href="https://vite.dev" target="_blank" rel="noreferrer">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank" rel="noreferrer">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <Card />
      <p className="read-the-docs">Click on the Vite and React logos to learn more</p>
    </ThemeProvider>
  )
}

export default App
로그인 후 복사

카드 구성요소 생성

// ./src/Card.tsx
import { useState } from 'react'
import { Theme } from './constants'
import { useThemeContext } from './theme'

function Card() {
  const { theme, setTheme } = useThemeContext()
  const [count, setCount] = useState(0)

  return (
    <div className='card'>
      <button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
      <p>
        Edit <code>src/App.tsx</code> and save to test HMR
      </p>
      <div>
        Themes:{' '}
        <select value={theme} onChange={(event) => setTheme(event.target.value as Theme)}>
          <option value={Theme.System}>System</option>
          <option value={Theme.Light}>Light</option>
          <option value={Theme.Dark}>Dark</option>
        </select>
      </div>
    </div>
  )
}

export default Card
로그인 후 복사

수화 오류 해결

테마를 서버 렌더링 메소드에 전달하여 서버 생성 HTML이 클라이언트측 렌더링과 일치하는지 확인하세요.

import { renderToString } from 'react-dom/server'
import App from './App'
import { Theme } from './constants'

export function render(theme: Theme) {
  return renderToString(<App theme={theme} />)
}
로그인 후 복사

스타일 추가

:root {
    color: #242424;
    background-color: rgba(255, 255, 255, 0.87);
}

:root.dark {
    color: rgba(255, 255, 255, 0.87);
    background-color: #242424;
}
로그인 후 복사

결론

이 기사에서는 SSR React 애플리케이션에서 원활한 테마를 구현하는 문제를 다루었습니다. 쿠키를 사용하고 클라이언트 측 논리와 서버 측 논리를 통합함으로써 수화 오류나 사용자 경험 중단 없이 밝은 테마, 어두운 테마, 시스템 기반 테마를 지원하는 강력한 시스템을 만들었습니다.

코드 탐색

  • : 반응-ssr-테마-예
  • SSR로 착륙: 전문 착륙

관련 기사

이것은 React를 사용한 SSR 시리즈의 일부입니다. 더 많은 기사를 기대해주세요!

  • 생산 준비가 완료된 SSR React 애플리케이션 구축
  • 스트리밍 및 동적 데이터를 사용한 고급 React SSR 기술
  • SSR React 애플리케이션에서 테마 설정

연결 상태 유지

저는 항상 피드백, 협업 또는 기술 아이디어 논의에 열려 있습니다. 언제든지 연락주세요!

  • 포트폴리오: maxh1t.xyz
  • 이메일: m4xh17@gmail.com

위 내용은 SSR React 애플리케이션에서 테마 설정의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿