Community코딩 & 개발github.com

analogjs/angular-ssr

Angular v20+에서 @angular/ssr을 사용하여 서버사이드 렌더링(SSR) 및 수화(hydration)를 구현합니다. SSR 설정, 수화 전략, 정적 페이지 사전 렌더링, 브라우저 전용 API 처리에 사용됩니다. SSR 구성, 수화 불일치 수정, 라우트 사전 렌더링, 또는 코드의 SSR 호환성 작업 시 트리거됩니다.

angular-ssr란 무엇인가요?

이 스킬은 Angular v20 이상 애플리케이션에서 @angular/ssr 패키지를 활용하여 서버사이드 렌더링(SSR)과 수화(hydration)를 설정하고 최적화하는 방법을 제공합니다. 개발자는 SSR 설정, 다양한 수화 전략(예: 전체 수화, 부분 수화, 지연 수화), 정적 페이지 사전 렌더링(prerendering), 브라우저 전용 API(sessionStorage, window 등)의 SSR 호환 처리, 그리고 수화 불일치(hydration mismatch) 오류를 진단하고 해결하는 작업을 수행할 수 있습니다. 주로 Angular CLI 기반 프로젝트에서 사용되며, Angular v20+의 독립형(standalone) 컴포넌트 및 새로운 제어 흐름(@if, @for)과 호환됩니다. 이 스킬의 핵심 가치는 기존 Angular 애플리케이션에 SSR을 통합하여 검색 엔진 최적화(SEO)와 초기 로드 성능을 향상시키고, 복잡한 수화 문제를 체계적으로 해결하는 데 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/analogjs/angular-skills/tree/main/skills/angular-ssr

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

angular-ssr은(는) 무엇을 하나요?

Angular v20+에서 @angular/ssr을 사용하여 서버사이드 렌더링(SSR) 및 수화(hydration)를 구현합니다. SSR 설정, 수화 전략, 정적 페이지 사전 렌더링, 브라우저 전용 API 처리에 사용됩니다. SSR 구성, 수화 불일치 수정, 라우트 사전 렌더링, 또는 코드의 SSR 호환성 작업 시 트리거됩니다.

설정

Angular CLI 프로젝트에 SSR을 추가합니다:

ng add @angular/ssr

기존 프로젝트에 수동으로 추가할 수도 있습니다:

npm install @angular/ssr

주요 작업

SSR 구성 확인

angular.json 파일에 SSR 관련 설정이 포함되어 있는지 확인하세요:

{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "ssr": {
              "entry": "src/server.ts"
            },
            "prerender": {
              "routes": [
                "/",
                "/about",
                "/contact"
              ]
            }
          }
        }
      }
    }
  }
}

서버 부트스트랩 (server.ts)

기본 server.ts 파일:

import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@angular/ssr';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';

export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/my-app/browser');
  const indexHtml = join(distFolder, 'index.html');

  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  server.get('*', (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

run();

수화 설정

Angular v20+에서는 provideClientHydration을 사용하여 다양한 수화 전략을 구성할 수 있습니다:

import { provideClientHydration, withNoHttpTransferCache, withEventReplay } from '@angular/platform-browser';

// 애플리케이션 구성에 추가
bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withNoHttpTransferCache(),
      withEventReplay()
    )
  ]
});

부분 수화 (Angular v20+)

부분 수화를 활성화하려면:

import { provideClientHydration, withPartialHydration } from '@angular/platform-browser';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withPartialHydration()
    )
  ]
});

브라우저 전용 API 처리

서버에서 브라우저 전용 API를 사용할 때 오류를 방지합니다:

import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { PLATFORM_ID, inject } from '@angular/core';

export class MyComponent {
  private platformId = inject(PLATFORM_ID);

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      // 브라우저에서만 실행되는 코드
      const token = localStorage.getItem('auth-token');
      // ...
    }

    if (isPlatformServer(this.platformId)) {
      // 서버에서만 실행되는 코드
      // (예: 메타데이터 설정)
    }
  }
}

혹은 afterNextRender를 사용합니다:

import { afterNextRender } from '@angular/core';

export class MyComponent {
  constructor() {
    afterNextRender(() => {
      // 이 코드는 브라우저에서 렌더링 후에만 실행됩니다.
      document.title = 'My Page';
    });
  }
}

수화 불일치 해결

수화 불일치 진단을 활성화합니다:

import { provideClientHydration, withDomHydrationErrorRecovery } from '@angular/platform-browser';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withDomHydrationErrorRecovery()
    )
  ]
});

일반적인 수화 불일치 원인:

  1. 동적 텍스트 콘텐츠: 서버와 클라이언트에서 다른 Date, Math.random() 값이 생성됨. afterNextRender 사용.
  2. 브라우저 전용 스타일: :host-context와 같은 CSS 클래스가 서버에서 다르게 적용됨.
  3. 서드파티 라이브러리: DOM을 직접 조작하는 라이브러리. isPlatformBrowser()로 감싸기.
  4. 주입된 HTML: innerHTML로 설정된 콘텐츠가 서버와 클라이언트에서 다름.

정적 페이지 사전 렌더링

angular.json에서 사전 렌더링할 라우트를 지정합니다:

"prerender": {
  "routes": [
    "/",
    "/products",
    "/about"
  ]
}

사전 렌더링을 실행합니다:

ng build

SSR 호환 코드 작성 모범 사례

  1. localStorage, sessionStorage 사용 금지: afterNextRender 또는 isPlatformBrowser()로 보호.
  2. window, document 참조 금지: 서버에서 정의되지 않음. afterNextRender 사용.
  3. ElementRef의 nativeElement 속성: 서버에서는 null일 수 있으므로 조건부 접근.
  4. 동적 데이터를 생성할 때 TransferState 사용: 서버에서 클라이언트로 데이터 전달.
import { TransferState, makeStateKey } from '@angular/core';

const COUNTER_KEY = makeStateKey<number>('counter');

@Component({
  // ...
})
export class CounterComponent {
  private transferState = inject(TransferState);
  private platformId = inject(PLATFORM_ID);

  counter: number;

  constructor() {
    if (isPlatformServer(this.platformId)) {
      this.counter = 42; // 서버에서 생성
      this.transferState.set(COUNTER_KEY, this.counter);
    } else {
      this.counter = this.transferState.get(COUNTER_KEY, 0); // 클라이언트에서 읽기
    }
  }
}

추가 리소스

Individual skills in this repo

This repo contains 9 individual skills — each has its own dedicated page.

analogjs/angular-component

Create modern Angular standalone components following v20+ best practices. Use for building UI components with signal-based inputs/outputs, OnPush change detection, host bindings, content projection, and lifecycle hooks. Triggers on component creation, refactoring class-based inputs to signals, adding host bindings, or implementing accessible interactive components.

analogjs/angular-di

Implement dependency injection in Angular v20+ using inject(), injection tokens, and provider configuration. Use for service architecture, providing dependencies at different levels, creating injectable tokens, and managing singleton vs scoped services. Triggers on service creation, configuring providers, using injection tokens, or understanding DI hierarchy.

analogjs/angular-directives

Create custom directives in Angular v20+ for DOM manipulation and behavior extension. Use for attribute directives that modify element behavior/appearance, structural directives for portals/overlays, and host directives for composition. Triggers on creating reusable DOM behaviors, extending element functionality, or composing behaviors across components. Note - use native @if/@for/@switch for control flow, not custom structural directives.

analogjs/angular-forms

Build signal-based forms in Angular v21+ using the new Signal Forms API. Use for form creation with automatic two-way binding, schema-based validation, field state management, and dynamic forms. Triggers on form implementation, adding validation, creating multi-step forms, or building forms with conditional fields. Signal Forms are experimental but recommended for new Angular projects. Don't use for template-driven forms without signals or third-party form libraries like Formly or ngx-formly.

analogjs/angular-http

Implement HTTP data fetching in Angular v20+ using resource(), httpResource(), and HttpClient. Use for API calls, data loading with signals, request/response handling, and interceptors. Triggers on data fetching, API integration, loading states, error handling, or converting Observable-based HTTP to signal-based patterns.

analogjs/angular-routing

Implement routing in Angular v20+ applications with lazy loading, functional guards, resolvers, and route parameters. Use for navigation setup, protected routes, route-based data loading, and nested routing. Triggers on route configuration, adding authentication guards, implementing lazy loading, or reading route parameters with signals.

analogjs/angular-signals

Implement signal-based reactive state management in Angular v20+. Use for creating reactive state with signal(), derived state with computed(), dependent state with linkedSignal(), and side effects with effect(). Triggers on state management questions, converting from BehaviorSubject/Observable patterns to signals, or implementing reactive data flows.

analogjs/angular-testing

Write unit and integration tests for Angular v20+ applications using Vitest or Jasmine with TestBed and modern testing patterns. Use for testing components with signals, OnPush change detection, services with inject(), and HTTP interactions. Triggers on test creation, testing signal-based components, mocking dependencies, or setting up test infrastructure. Don't use for E2E testing with Cypress or Playwright, or for testing non-Angular JavaScript/TypeScript code.

analogjs/angular-tooling

Use Angular CLI and development tools effectively in Angular v20+ projects. Use for project setup, code generation, building, testing, and configuration. Triggers on creating new projects, generating components/services/modules, configuring builds, running tests, or optimizing production builds. Don't use for Nx workspace commands, custom Webpack configurations, or non-Angular CLI build systems like Vite standalone or esbuild direct usage.

관련 스킬