Files
2nd/10_Wiki/Topics/Frontend/렌더링 블로킹 방지를 위한 CSS 분할 및 로딩 최적화.md
T
koriweb d8a80f6272 chore(wiki): dangling 링크 canonical 정규화 (768파일/1200건)
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해
끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은
과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업.
도구: Datacollect/scripts/link_reconcile_apply.mjs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:24:15 +09:00

5.6 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-렌더링-블로킹-방지를-위한-css-분할-및-로딩-최적화 렌더링 블로킹 방지를 위한 CSS 분할 및 로딩 최적화 10_Wiki/Topics verified self
Critical CSS
CSS Code Splitting
Render-blocking CSS
none A 0.95 applied
frontend
css
performance
critical-css
lcp
2026-05-10 pending
language framework
css vite

렌더링 블로킹 방지를 위한 CSS 분할 및 로딩 최적화

매 한 줄

"매 CSS는 default 로 render-blocking". <link rel="stylesheet"> 가 도착할 때까지 브라우저는 paint 안 함. Critical CSS inline + non-critical async load + route-based split → LCP 50%+ 개선. 2026 표준은 Vite/Rollup chunk + media="print" trick.

매 핵심

매 왜 CSS 가 blocking 인가

  • HTML parser 가 <link rel="stylesheet"> 만나면 CSSOM 만들 때까지 paint X.
  • CSSOM 없이는 element 의 final style 알 수 없음 → FOUC 방지 위해 block.
  • 1 round-trip (HTTP) + parse + match = 100-500ms LCP cost.

매 분할 전략

  • Critical CSS: above-the-fold 의 minimal CSS — <style> 으로 inline.
  • Async load: 나머지 CSS 는 non-blocking — media="print" swap trick / rel="preload".
  • Route-based split: 각 route 마다 별도 CSS chunk — 안 쓰는 CSS 안 download.
  • Component-scoped: CSS-in-JS / CSS Modules → 자동 split.

매 응용

  1. Marketing landing (LCP 핵심).
  2. SSR app (FOUC 방지 critical inline).
  3. SPA (route chunk).

💻 패턴

Critical CSS inline (manual)

<head>
  <style>
    /* 매 above-the-fold only — header, hero, font-face */
    body { font-family: system-ui; margin: 0; }
    .hero { height: 80vh; background: #111; color: #fff; }
  </style>
  <link
    rel="preload"
    href="/css/main.css"
    as="style"
    onload="this.onload=null;this.rel='stylesheet'"
  />
  <noscript><link rel="stylesheet" href="/css/main.css" /></noscript>
</head>

Critical CSS extraction (Vite plugin)

// vite.config.ts
import { defineConfig } from 'vite';
import critical from 'rollup-plugin-critical';

export default defineConfig({
  plugins: [
    critical({
      criticalUrl: 'https://example.com/',
      criticalBase: './dist/',
      criticalPages: [{ uri: '', template: 'index' }],
      criticalConfig: { inline: true, dimensions: [{ width: 375, height: 812 }, { width: 1920, height: 1080 }] },
    }),
  ],
});

print media swap trick (no JS)

<link rel="stylesheet" href="/css/main.css" media="print" onload="this.media='all'" />

브라우저가 media="print" 를 non-blocking 으로 download → onload 시 all 로 swap.

Route-based split (Next.js)

// app/products/page.tsx — 매 자동으로 별도 CSS chunk.
import './products.css'; // 매 only loaded on /products.

CSS Modules (component-scoped)

import styles from './Button.module.css';
<button className={styles.primary}>Click</button>;
// 매 build 시 hash → only used styles bundled.

Tailwind v4 (oxide engine, 2026)

/* main.css */
@import "tailwindcss";

@theme {
  --color-brand: oklch(0.7 0.2 250);
}

PurgeCSS 자동 — 매 unused class 제거. 최종 bundle 5-10KB.

Preload key fonts + CSS together

<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/css/main.css" as="style" />

CSS containment for paint isolation

.card {
  contain: content; /* 매 layout + paint + style 격리 */
  content-visibility: auto;
  contain-intrinsic-size: 0 200px;
}

HTTP/2 push 대체 — 103 Early Hints

HTTP/1.1 103 Early Hints
Link: </css/main.css>; rel=preload; as=style
Link: </js/app.js>; rel=preload; as=script

Cloudflare/Vercel 자동 지원 — TTFB 안 기다리고 preload 시작.

Detect unused CSS (Chrome DevTools)

# 매 Coverage tab → Record → reload → unused % 확인.
# 매 80%+ unused = split 필요.

매 결정 기준

상황 Approach
Marketing landing Critical inline + async rest.
SPA Route-based split + CSS Modules.
SSR (Next/Remix) 매 자동 split — Tailwind v4 면 충분.
Component lib CSS-in-JS 또는 CSS Modules — tree-shake.
Legacy multi-page print media trick + critical extraction.

기본값: Tailwind v4 + Vite/Next 자동 chunk + <link rel="preload"> for above-fold.

🔗 Graph

🤖 LLM 활용

언제: LCP > 2.5s, CSS bundle > 100KB, FOUC 발생. 언제 X: 매 internal tool (LCP 무관), 매 tiny site (single CSS file 충분).

안티패턴

  • Single mega CSS: 매 1MB CSS load — purge + split.
  • @import chain: serial load — bundle 로 합치거나 <link> 직접.
  • Inline ALL CSS: HTML 비대 → critical only.
  • No font-display: FOIT (invisible text) → font-display: swap.
  • CSS in <body>: legacy hack 매 — <head> 에서 preload.
  • Unused frameworks: Bootstrap full 250KB 인데 button 만 사용.

🧪 검증 / 중복

  • Verified (web.dev/extract-critical-css, MDN preload, Vite docs, Tailwind v4 docs).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — critical CSS + Tailwind v4 + Early Hints