RedwoodJS 렌더링 엔진 완벽 분석: 템플릿부터 페이지 생성까지

RedwoodJS는 React 기반의 풀스택 프레임워크로, GraphQL과 결합하여 강력한 서버사이드 및 정적 생성 기능을 제공한다. 본 문서에서는 RedwoodJS의 렌더링 메커니즘, Cell 패턴, 그리고 실전 최적화 전략을 심층적으로 다룬다.

렌더링 아키텍처 이해

RedwoodJS는 세 가지 렌더링 전략을 제공한다. 각 전략은 특정 사용 사례에 최적화되어 있으며, 개발자는 페이지별로 적절한 방식을 선택할 수 있다.

모드실행 시점적합한 상황
CSR (Client-Side Rendering)브라우저 런타임대시보드, 관리자 패널
SSR (Server-Side Rendering)요청 시 서버에서SEO 중시 콘텐츠
SSG (Static Site Generation)빌드 시점마케팅 페이지, 문서

Cell 패턴: 선언적 데이터 페칭

RedwoodJS의 독창적인 Cell 패턴은 데이터 요청의 라이프사이클을 선언적으로 관리한다. 단일 파일 내에서 로딩, 성공, 실패, 빈 상태를 모두 정의한다.

// src/components/ArticleCell/ArticleCell.jsx
export const FETCH_ARTICLE = gql`
  query FetchArticle($slug: String!) {
    article: postBySlug(slug: $slug) {
      headline
      body
      publishedAt
      author {
        fullName
        avatarUrl
      }
    }
  }
`

export const Pending = () => (
  <Skeleton variant="article" rows={5} />
)

export const Rejected = ({ error }) => (
  <ErrorBoundary 
    title="콘텐츠를 불러올 수 없습니다"
    detail={error.message}
    retryable
  />
)

export const Empty = () => (
  <NotFound 
    resource="게시글"
    suggestion="다른 키워드로 검색해보세요"
  />
)

export const Fulfilled = ({ article }) => (
  <article className="prose lg:prose-xl">
    <header>
      <h1>{article.headline}</h1>
      <AuthorProfile author={article.author} date={article.publishedAt} />
    </header>
    <div dangerouslySetInnerHTML={{ __html: article.body }} />
  </article>
)

레이아웃 구성과 중첩 라우팅

RedwoodJS는 파일 시스템 기반 라우팅과 레이아웃 상속을 지원한다. 중첩된 레이아웃을 통해 UI 일관성을 유지하면서도 유연한 페이지 구성이 가능하다.

// src/layouts/AppLayout/AppLayout.jsx
import { MetaTags } from '@redwoodjs/web'

const AppLayout = ({ children, breadcrumbs }) => {
  const { currentUser, logOut } = useAuth()
  
  return (
    <div className="app-container">
      <MetaTags titleTemplate="%s | MyApp" />
      
      <TopNavigation 
        user={currentUser} 
        onSignOut={logOut}
        items={navigationItems}
      />
      
      <aside className="sidebar">
        <BreadcrumbTrail items={breadcrumbs} />
        <SecondaryMenu />
      </aside>
      
      <main className="content-area">
        {children}
      </main>
      
      <ToastContainer position="bottom-right" />
    </div>
  )
}

export default AppLayout

동적 세그먼트와 정적 생성

동적 라우트에 대한 정적 페이지를 생성하려면 getStaticPaths 함수를 활용한다. 빌드 시점에 모든 가능한 경로를 미리 생성하여 CDN 배포에 최적화한다.

// src/pages/[category]/[productId].jsx
import { ProductCell } from 'src/components/ProductCell'

export const getStaticPaths = async () => {
  const categories = await db.category.findMany({
    include: { products: { select: { slug: true } } }
  })
  
  const paths = categories.flatMap(cat => 
    cat.products.map(prod => ({
      params: { 
        category: cat.handle,
        productId: prod.slug 
      }
    }))
  )
  
  return { paths, fallback: 'blocking' }
}

const ProductDetailPage = ({ category, productId }) => {
  return (
    <ProductCell 
      categoryHandle={category} 
      productSlug={productId} 
    />
  )
}

export default ProductDetailPage

성능 최적화 패턴

코드 분할과 지연 로딩

import { lazy, Suspense } from 'react'

const HeavyChart = lazy(() => import('src/components/AnalyticsChart'))

const DashboardPage = () => {
  return (
    <div className="dashboard">
      <KpiCards />
      <Suspense fallback={<ChartPlaceholder />}>
        <HeavyChart dateRange={last30Days} />
      </Suspense>
    </div>  
  )
}

이미지 최적화 구성

// redwood.toml
[web]
  port = 8910
  apiUrl = "/.netlify/functions"

[experimental]
  streamingSsr = true

[images]
  domains = ["cdn.example.com", "images.unsplash.com"]
  sizes = [640, 1280, 1920]
  formats = ["webp", "avif"]

테스트 전략

RedwoodJS는 Jest와 Testing Library를 기본으로 제공한다. Cell 컴포넌트의 각 상태를 개별적으로 테스트한다.

// ArticleCell.test.jsx
import { render, screen } from '@redwoodjs/testing/web'
import { Pending, Rejected, Fulfilled } from './ArticleCell'

describe('ArticleCell 상태', () => {
  it('로딩 중 스켈레톤을 표시한다', () => {
    render(<Pending />)
    expect(screen.getByRole('progressbar')).toBeInTheDocument()
  })

  it('에러 발생 시 재시도 버튼을 제공한다', () => {
    const networkError = new Error('ECONNREFUSED')
    render(<Rejected error={networkError} />)
    expect(screen.getByRole('button', { name: /다시 시도/i })).toBeEnabled()
  })

  it('정상 응답 시 콘텐츠를 렌더링한다', () => {
    const mockArticle = {
      headline: 'RedwoodJS 심화',
      body: '<p>상세 내용</p>',
      author: { fullName: '김개발', avatarUrl: null }
    }
    render(<Fulfilled article={mockArticle} />)
    expect(screen.getByText('RedwoodJS 심화')).toBeInTheDocument()
  })
})

배포 구성 예시

Netlify, Vercel, Render 등 다양한 플랫폼에 최적화된 설정을 제공한다. Edge Function을 활용한 지역별 캐싱 전략도 지원한다.

// netlify.toml
[build]
  command = "yarn rw build"
  publish = "web/dist"

[[edge_functions]]
  function = "geo-redirect"
  path = "/api/*"

[context.production.environment]
  NODE_ENV = "production"
  REDWOOD_API_URL = "/.netlify/functions"

태그: RedwoodJS React GraphQL SSR SSG

9월 8일 12:41에 게시됨