Skip to content

[2팀 정민기] Chapter 4-1 성능 최적화#2

Open
jeongmingi123 wants to merge 15 commits intohanghae-plus:mainfrom
jeongmingi123:main
Open

[2팀 정민기] Chapter 4-1 성능 최적화#2
jeongmingi123 wants to merge 15 commits intohanghae-plus:mainfrom
jeongmingi123:main

Conversation

@jeongmingi123
Copy link

@jeongmingi123 jeongmingi123 commented Aug 30, 2025

과제 체크포인트

배포 링크

기본 : https://jeongmingi123.github.io/front_6th_chapter4-1/vanilla/
심화 : https://jeongmingi123.github.io/front_6th_chapter4-1/react/

기본과제 (Vanilla SSR & SSG)

Express SSR 서버

  • Express 미들웨어 기반 서버 구현
  • 개발/프로덕션 환경 분기 처리
  • HTML 템플릿 치환 (<!--app-html-->, <!--app-head-->)

서버 사이드 렌더링

  • 서버에서 동작하는 Router 구현
  • 서버 데이터 프리페칭 (상품 목록, 상품 상세)
  • 서버 상태관리 초기화

클라이언트 Hydration

  • window.__INITIAL_DATA__ 스크립트 주입
  • 클라이언트 상태 복원
  • 서버-클라이언트 데이터 일치

Static Site Generation

  • 동적 라우트 SSG (상품 상세 페이지들)
  • 빌드 타임 페이지 생성
  • 파일 시스템 기반 배포

심화과제 (React SSR & SSG)

React SSR

  • renderToString 서버 렌더링
  • TypeScript SSR 모듈 빌드
  • Universal React Router (서버/클라이언트 분기)
  • React 상태관리 서버 초기화

React Hydration

  • Hydration 불일치 방지
  • 클라이언트 상태 복원

Static Site Generation

  • 동적 라우트 SSG (상품 상세 페이지들)
  • 빌드 타임 페이지 생성
  • 파일 시스템 기반 배포

구현 과정 돌아보기

가장 어려웠던 부분과 해결 과정

// productUseCase.ts
export const loadProductsAndCategories = async () => {
  router.query = { current: undefined }; // 항상 첫 페이지로 초기화
  productStore.dispatch({
    type: PRODUCT_ACTIONS.SETUP,
    payload: {
      ...initialProductState,
      loading: true,
      status: "pending",
    },
  });

심화과제인 react 구현중에, router.query = { current: undefined }; 로 인해 렌더링 이슈가 발생하였고,
해당 코드를 지우면서 timeout이 계속 발생하여 react 심화과제 CSR 렌더링 이슈가 발생하여 CSR react 테스트 코드가 다 실패하였습니다.

router.query = { current: undefined };를 제거하면
라우터의 초기 상태가 빈 객체 {}가 되고, API 호출 시 current 파라미터가 전달되지 않아 서버와 동일한 기본값(1)이 사용됨.
SSR과 CSR의 결과가 일치하게 되어 하이드레이션 이슈가 해결되었음. (이걸로 한 6시간쓴듯..)

구현하면서 새롭게 알게 된 개념

성능 최적화 관점에서의 인사이트

사실 과제 이해하는데 오래걸려서 .. 성능 최적화까지는 하지 못했음

학습 갈무리

Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까?

현재 아키텍처의 한계점

  1. 단일 서버 의존성: Express 서버 하나에 모든 SSR 로직이 집중되어 있어 수평 확장이 어려움
  2. 메모리 기반 상태 관리: 서버 재시작 시 모든 상태가 초기화됨
  3. 동기식 렌더링: 모든 페이지가 순차적으로 렌더링되어 병목 발생
  4. 캐싱 전략 부재: 동일한 요청에 대한 캐싱 메커니즘 없음

개선 방안

  1. 마이크로서비스 아키텍처 도입

    • API 서버와 렌더링 서버 분리
    • 각 서비스별 독립적인 스케일링
    • 서비스 간 통신을 위한 gRPC 또는 HTTP/2 활용
  2. 캐싱 계층 구축

    // Redis 기반 페이지 캐싱
    const cacheKey = `page:${url}:${JSON.stringify(query)}`;
    const cached = await redis.get(cacheKey);
    if (cached) return JSON.parse(cached);
    
    const rendered = await render(url, query);
    await redis.setex(cacheKey, 300, JSON.stringify(rendered)); // 5분 캐시
  3. 비동기 렌더링 및 스트리밍

    // React 18의 Suspense와 스트리밍 활용
    const stream = renderToPipeableStream(React.createElement(App), {
      onShellReady() {
        res.setHeader('Content-Type', 'text/html');
        stream.pipe(res);
      }
    });
  4. CDN 통합

    • 정적 자산과 SSG 페이지를 CDN에 배포
    • Edge Computing을 통한 지역별 최적화

Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?

Cloudflare Workers로 마이그레이션 시 고려사항

  1. 런타임 제약사항

    // 현재: Node.js API 사용
    import fs from "node:fs/promises";
    
    // Workers: KV Storage 사용
    const data = await env.PRODUCTS_KV.get("products");
  2. Cold Start 최적화

    // 번들 크기 최소화
    import { renderToString } from "react-dom/server";
    // 필요한 부분만 import하여 번들 크기 줄이기
    
    // 웜업 전략
    export default {
      async fetch(request, env, ctx) {
        // 핫 패스 최적화
        if (request.url.includes('/api/health')) {
          return new Response('OK');
        }
        // ... 렌더링 로직
      }
    };
  3. 메모리 제한 대응

    // 대용량 데이터 처리 시 스트리밍 활용
    const stream = new ReadableStream({
      start(controller) {
        // 청크 단위로 데이터 전송
        controller.enqueue(chunk);
      }
    });

Vercel Edge Functions 활용

  1. Edge Runtime 최적화

    export const config = {
      runtime: 'edge',
      regions: ['iad1', 'sfo1'] // 지역별 배포
    };
    
    export default async function handler(req) {
      // Edge에서 실행되는 최적화된 렌더링
      const html = await renderToStaticMarkup(Component);
      return new Response(html, {
        headers: { 'Content-Type': 'text/html' }
      });
    }
  2. ISR (Incremental Static Regeneration) 활용

    // 빌드 타임 + 런타임 재생성
    export async function getStaticProps() {
      return {
        props: { data },
        revalidate: 60 // 60초마다 재생성
      };
    }

Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?

주요 병목 지점

  1. 서버 렌더링 시 CPU 집약적 작업

    // 현재: 모든 상품을 메모리에서 필터링
    const filteredProducts = filterProducts(items, query);
    
    // 개선: 데이터베이스 쿼리 최적화
    const products = await db.products
      .where('title', 'like', `%${search}%`)
      .orderBy('price', sort === 'price_asc' ? 'asc' : 'desc')
      .limit(limit)
      .offset(offset);
  2. 메모리 누수 위험

    // 현재: 전역 변수로 데이터 보관
    let template;
    let render;
    
    // 개선: WeakMap 활용 및 메모리 관리
    const renderCache = new WeakMap();
    const templateCache = new Map();
    
    // 주기적 캐시 정리
    setInterval(() => {
      if (templateCache.size > 100) {
        templateCache.clear();
      }
    }, 60000);
  3. 번들 크기 최적화

    // 현재: 전체 라이브러리 import
    import { renderToString } from "react-dom/server";
    
    // 개선: Tree Shaking 및 코드 스플리팅
    const { renderToString } = await import("react-dom/server");
    
    // Vite 설정 최적화
    export default defineConfig({
      build: {
        rollupOptions: {
          output: {
            manualChunks: {
              vendor: ['react', 'react-dom'],
              utils: ['lodash', 'date-fns']
            }
          }
        }
      }
    });

성능 모니터링 도입

// 성능 메트릭 수집
const performanceObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.duration}ms`);
  }
});

performanceObserver.observe({ entryTypes: ['measure'] });

Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?

빌드 시간 최적화

  1. 병렬 처리 구현

    // 현재: 순차 처리
    for (const productId of productIds) {
      await generateProductPage(productId);
    }
    
    // 개선: 병렬 처리 (배치 단위)
    const BATCH_SIZE = 10;
    const batches = chunk(productIds, BATCH_SIZE);
    
    for (const batch of batches) {
      await Promise.all(
        batch.map(productId => generateProductPage(productId))
      );
    }
  2. 증분 빌드 구현

    // 변경된 상품만 재빌드
    const lastBuildTime = await getLastBuildTime();
    const changedProducts = await getChangedProducts(lastBuildTime);
    
    if (changedProducts.length > 0) {
      await generateProductPages(changedProducts);
    }
  3. 메모리 사용량 관리

    // 스트리밍 방식으로 파일 생성
    const writeStream = fs.createWriteStream(outputPath);
    
    for (const product of products) {
      const html = await renderProduct(product);
      writeStream.write(html);
      
      // 메모리 압박 시 가비지 컬렉션 유도
      if (process.memoryUsage().heapUsed > 500 * 1024 * 1024) {
        global.gc && global.gc();
      }
    }

CDN 캐시 무효화 전략

// 상품 업데이트 시 관련 페이지 무효화
async function invalidateProductCache(productId) {
  const paths = [
    `/product/${productId}/`,
    `/`, // 홈페이지도 무효화 (관련 상품 목록)
  ];
  
  await Promise.all(
    paths.map(path => cdn.purge(path))
  );
}

부분 재빌드 구현

// 상품별 의존성 그래프 관리
const dependencyGraph = {
  'product-1': ['home', 'category-electronics'],
  'product-2': ['home', 'category-clothing']
};

async function rebuildAffectedPages(changedProductId) {
  const affectedPages = dependencyGraph[changedProductId] || [];
  await Promise.all(
    affectedPages.map(page => rebuildPage(page))
  );
}

Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?

주요 UX 이슈

  1. 인터랙션 차단 시간

    // 현재: 전체 앱이 하이드레이션 완료까지 대기
    
    // 개선: Progressive Enhancement
    const ProgressiveApp = () => {
      const [isHydrated, setIsHydrated] = useState(false);
      
      useEffect(() => {
        setIsHydrated(true);
      }, []);
      
      return (
        <div>
          {/* 서버 렌더링된 콘텐츠는 즉시 표시 */}
          <StaticContent />
          {isHydrated && <InteractiveContent />}
        </div>
      );
    };
  2. 레이아웃 시프트 (CLS) 방지

    // Skeleton UI로 공간 확보
    const ProductCardSkeleton = () => (
      <div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
        <div className="aspect-square bg-gray-200 animate-pulse" />
        <div className="p-3">
          <div className="h-4 bg-gray-200 rounded animate-pulse mb-2" />
          <div className="h-3 bg-gray-200 rounded animate-pulse mb-2" />
          <div className="h-6 bg-gray-200 rounded animate-pulse" />
        </div>
      </div>
    );
  3. 로딩 상태 표시

    // 하이드레이션 진행 상태 표시
    const HydrationProgress = () => {
      const [progress, setProgress] = useState(0);
      
      useEffect(() => {
        const interval = setInterval(() => {
          setProgress(prev => Math.min(prev + 10, 90));
        }, 100);
        
        return () => clearInterval(interval);
      }, []);
      
      return (
        <div className="fixed top-0 left-0 w-full h-1 bg-gray-200">
          <div 
            className="h-full bg-blue-600 transition-all duration-300"
            style={{ width: `${progress}%` }}
          />
        </div>
      );
    };

Islands Architecture 적용

// 필요한 부분만 하이드레이션
const ProductCard = ({ product, isInteractive = false }) => {
  if (!isInteractive) {
    // 정적 렌더링
    return <StaticProductCard product={product} />;
  }
  
  // 인터랙티브 렌더링
  return <InteractiveProductCard product={product} />;
};

// 클라이언트에서 필요한 카드만 하이드레이션
document.addEventListener('DOMContentLoaded', () => {
  const visibleCards = document.querySelectorAll('.product-card[data-in-viewport="true"]');
  visibleCards.forEach(card => {
    hydrateCard(card);
  });
});

Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?

모니터링 및 로깅 체계

  1. 성능 모니터링

    // Core Web Vitals 측정
    import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
    
    getCLS(console.log);
    getFID(console.log);
    getFCP(console.log);
    getLCP(console.log);
    getTTFB(console.log);
    
    // 서버 성능 모니터링
    const serverMetrics = {
      renderTime: Date.now() - startTime,
      memoryUsage: process.memoryUsage(),
      cpuUsage: process.cpuUsage()
    };
    
    await metricsCollector.record('ssr_performance', serverMetrics);
  2. 에러 핸들링 및 Fallback 전략

    // Circuit Breaker 패턴
    class RenderService {
      constructor() {
        this.failureCount = 0;
        this.lastFailureTime = 0;
        this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
      }
      
      async render(url, query) {
        if (this.state === 'OPEN') {
          if (Date.now() - this.lastFailureTime > 60000) {
            this.state = 'HALF_OPEN';
          } else {
            return this.getFallbackResponse();
          }
        }
        
        try {
          const result = await this.doRender(url, query);
          this.onSuccess();
          return result;
        } catch (error) {
          this.onFailure();
          return this.getFallbackResponse();
        }
      }
    }
  3. A/B 테스트 적용

    // 기능 플래그 기반 A/B 테스트
    const FeatureFlags = {
      NEW_PRODUCT_LAYOUT: 'new_product_layout',
      ENHANCED_SEARCH: 'enhanced_search'
    };
    
    const renderWithFeatureFlags = async (url, query, userId) => {
      const flags = await featureFlagService.getFlags(userId);
      
      if (flags[FeatureFlags.NEW_PRODUCT_LAYOUT]) {
        return renderWithNewLayout(url, query);
      }
      
      return renderWithLegacyLayout(url, query);
    };

보안 고려사항

  1. XSS 방지

    // 서버 렌더링 시 XSS 방지
    import { escape } from 'html-escaper';
    
    const safeRender = (content) => {
      return escape(content);
    };
  2. CSP (Content Security Policy) 설정

    // Express에서 CSP 헤더 설정
    app.use((req, res, next) => {
      res.setHeader('Content-Security-Policy', 
        "default-src 'self'; " +
        "script-src 'self' 'unsafe-inline'; " +
        "style-src 'self' 'unsafe-inline'; " +
        "img-src 'self' data: https:;"
      );
      next();
    });

Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?

직접 구현의 장점

  1. 학습 효과와 깊은 이해

    • SSR/SSG의 내부 동작 원리를 직접 경험
    • 성능 최적화 포인트를 정확히 파악
    • 문제 발생 시 근본 원인 추적 가능
  2. 커스터마이징 자유도

    // 특수한 요구사항에 맞는 최적화 가능
    const customRender = async (url, query) => {
      // 특정 페이지는 다른 렌더링 전략 적용
      if (url.includes('/admin')) {
        return renderWithAdminLayout(url, query);
      }
      
      // 일반 페이지는 표준 렌더링
      return renderWithStandardLayout(url, query);
    };
  3. 번들 크기 최적화

    • 필요한 기능만 구현하여 번들 크기 최소화
    • 프레임워크의 불필요한 기능 제거 가능

직접 구현의 단점

  1. 유지보수 비용

    • 버그 수정 및 보안 패치를 직접 관리해야 함
    • 새로운 기능 추가 시 프레임워크 대비 개발 시간 증가
  2. 생태계와 커뮤니티 지원 부족

    • 플러그인, 미들웨어 등이 제한적
    • 문제 해결 시 커뮤니티 지원 부족
  3. 안정성과 검증 부족

    • 다양한 엣지 케이스에 대한 검증 필요
    • 프로덕션 환경에서의 안정성 확보 어려움

하이브리드 접근법

// Next.js의 장점을 활용하면서 커스터마이징
// next.config.js
module.exports = {
  experimental: {
    // 커스텀 렌더링 로직 추가
    customServer: true,
  },
  
  // 필요한 기능만 선택적 사용
  webpack: (config) => {
    // 커스텀 웹팩 설정
    return config;
  }
};

Q8. Next.js를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?

Next.js SSG 최적화 설정

  1. getStaticProps와 getStaticPaths 활용

    // pages/products/[id].js
    export async function getStaticPaths() {
      // 모든 상품 ID 가져오기
      const products = await fetchAllProducts();
      const paths = products.map(product => ({
        params: { id: product.id }
      }));
      
      return {
        paths,
        fallback: 'blocking' // 새로운 상품은 런타임에 생성
      };
    }
    
    export async function getStaticProps({ params }) {
      const product = await fetchProduct(params.id);
      
      return {
        props: { product },
        revalidate: 3600 // 1시간마다 재생성
      };
    }
  2. ISR (Incremental Static Regeneration) 활용

    // pages/index.js
    export async function getStaticProps() {
      const products = await fetchProducts();
      
      return {
        props: { products },
        revalidate: 60 // 60초마다 재생성
      };
    }
  3. 빌드 최적화

    // next.config.js
    module.exports = {
      // 이미지 최적화
      images: {
        domains: ['example.com'],
        formats: ['image/webp', 'image/avif']
      },
      
      // 번들 분석
      webpack: (config, { isServer }) => {
        if (!isServer) {
          config.resolve.fallback = {
            ...config.resolve.fallback,
            fs: false
          };
        }
        return config;
      },
      
      // 압축
      compress: true,
      
      // 실험적 기능
      experimental: {
        optimizeCss: true,
        optimizePackageImports: ['react-icons']
      }
    };

배포 전략

  1. Vercel 배포

    # vercel.json
    {
      "builds": [
        {
          "src": "package.json",
          "use": "@vercel/next"
        }
      ],
      "functions": {
        "pages/api/**/*.js": {
          "maxDuration": 30
        }
      }
    }
  2. 다른 플랫폼 배포

    # Dockerfile
    FROM node:18-alpine AS deps
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --only=production
    
    FROM node:18-alpine AS builder
    WORKDIR /app
    COPY . .
    COPY --from=deps /app/node_modules ./node_modules
    RUN npm run build
    
    FROM node:18-alpine AS runner
    WORKDIR /app
    ENV NODE_ENV production
    COPY --from=builder /app/public ./public
    COPY --from=builder /app/.next/standalone ./
    COPY --from=builder /app/.next/static ./.next/static
    
    EXPOSE 3000
    CMD ["node", "server.js"]
  3. CDN 설정

    // next.config.js
    module.exports = {
      assetPrefix: process.env.NODE_ENV === 'production' 
        ? 'https://cdn.example.com' 
        : '',
      
      // 정적 파일 캐싱
      async headers() {
        return [
          {
            source: '/static/:path*',
            headers: [
              {
                key: 'Cache-Control',
                value: 'public, max-age=31536000, immutable'
              }
            ]
          }
        ];
      }
    };

성능 모니터링

// pages/_app.js
import { Analytics } from '@vercel/analytics/react';

export default function App({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />
      <Analytics />
    </>
  );
}

// 성능 측정
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  // Vercel Analytics 또는 다른 분석 도구로 전송
  analytics.track('web-vital', metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

코드 품질 향상

자랑하고 싶은 구현

없음

개선하고 싶은 부분

이번에 과제를 하면서, 에러가 어떻게 발생하는지 제대로 처리를 못해서 에러 처리를 제대로 못한거같습니다. 로깅을 추가하고, 에러 페이지 컴포넌트 및 에러 타입에 따른 응답에 대한 내용도 더 추가할 것 같습니다.
또한 환경 설정함수나 유틸 함수도 적절하게 분리를 못해서 코드가 난잡해지고 중복 현상도 많이 발생했던거 같아 다음과 같이 바꿀 것 같습니다.

  1. 구조적 분리
    환경 설정 분리: config/ 폴더에 환경별 설정 파일 분리
    미들웨어 분리: SSR 렌더링 로직을 별도 미들웨어 파일로 분리
    유틸리티 함수 분리: URL 정규화, 에러 처리 등을 별도 모듈로 분리

  2. 에러 처리 개선
    에러 타입별 처리: 404, 500 등 에러 타입에 따른 다른 응답
    에러 로깅: 구조화된 로깅 시스템 추가 도입
    에러 페이지: 에러 페이지 컴포넌트 추가

리팩토링 계획

ServerRouter.ts 부분을 함수형으로 리팩토링을 진행할 것 같습니다.

/**
 * 서버사이드 라우터
 */
import { BaseRouter } from "./BaseRouter.js";

export class ServerRouter extends BaseRouter {
  #currentUrl = "/";
  #origin = "http://localhost";
  #query = {};

  constructor(baseUrl = "") {
    super(baseUrl);
  }

  get query() {
    return this.#query;
  }

  set query(newQuery) {
    // 서버사이드에서는 Express에서 이미 파싱된 쿼리 객체를 직접 저장
    this.#query = newQuery || {};
  }

  getCurrentUrl() {
    return this.#currentUrl;
  }

  getOrigin() {
    return this.#origin;
  }

  /**
   * 서버 URL 설정
   * @param {string} url - 요청 URL
   * @param {string} [origin] - 서버 origin (선택적)
   */
  setUrl(url, origin = "http://localhost") {
    this.#currentUrl = url;
    this.#origin = origin;
    this.updateRoute(this.getCurrentUrl());
  }

  /**
   * 서버사이드에서는 네비게이션 불가
   */
  push() {
    throw new Error("Navigation is not supported in server-side routing");
  }

  /**
   * 라우터 시작
   */
  start() {
    this.updateRoute(this.getCurrentUrl());
  }
}

개인적으로 클래스도 좋아하지만, typescript는 함수형으로 많이 만들기에 다음과 같이 변경할 것 같습니다.

const createServerRouter = (baseUrl = "") => {
  // 상태 (클로저로 캡슐화)
  let currentUrl = "/";
  let origin = "http://localhost";
  let query = {};
  
  const baseRouter = createBaseRouter(baseUrl);
  
  return {
    // getter/setter 함수들
    getQuery: () => query,
    setQuery: (newQuery) => { query = newQuery || {}; },
    
    getCurrentUrl: () => currentUrl,
    getOrigin: () => origin,
    setUrl: (url, originParam) => { /* 구현 */ },
    push: () => { throw new Error("..."); },
    start: () => { /* 구현 */ }
  };
};

학습 연계

다음 학습 목표

Google Lighthouse와 같은 것을 사용하여, 사용자가 페이지를 실제로 보고 상호작용까지의 과정을 한번 측정해보고싶습니다.
이런 것을 사용했을 때 얼마나 사용자가 성능 체감이 되는지 한번 체크를 해보고싶습니다.

실무 적용 계획

회사에서 이전에 만든 인수인계 사이트를 SSR, SSG를 적용하여 마이그레이션 하여 만들어 보고 싶습니다. SSG와 SSR을 적절히 사용하여, 이 보다 웹 속도처리부분에서 팀원들에게 향상된 부분을 보여주고 싶음.

리뷰 받고 싶은 내용

// productUseCase.ts
export const loadProductsAndCategories = async () => {
  router.query = { current: undefined }; // 항상 첫 페이지로 초기화
  productStore.dispatch({
    type: PRODUCT_ACTIONS.SETUP,
    payload: {
      ...initialProductState,
      loading: true,
      status: "pending",
    },
  });

심화과제 react 구현중에 처음에는 다 CSR 관련 테스트코드가 통과했으나, SSR 및 SSG 구현하고 나니까 CSR (port 5175번) dev 테스트 코드만 전체 실패되었습니다.

그리하여, loadProductsAndCategories 함수에서 router.query = { current: undefined }; 로 인해 테스트 코드가 계속 렌더링 이슈가 발생했다고 생각했습니다.

이 후 router.query = { current: undefined }를 제거하였고 다음과 같이 생각했습니다.

  1. 라우터의 초기 상태가 빈 객체 {}가 됨.
  2. API 호출 시 current 파라미터가 전달되지 않아 서버와 동일한 기본값이 사용 됨
  3. SSR과 CSR의 결과가 일치하게 되어 하이드레이션 이슈가 해결됨.

이는 초기 상태의 일관성을 똑같이 만들어서 서버와 클라이언트 간의 렌더링 결과를 동일하게 만든다고 생각하였는데 이게 맞을까요??

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

Successfully merging this pull request may close these issues.

1 participant