d8a80f6272
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해 끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은 과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업. 도구: Datacollect/scripts/link_reconcile_apply.mjs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.5 KiB
7.5 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-style-registry | Style Registry | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
Style Registry
매 한 줄
"매 SSR streaming 시 CSS-in-JS 의 styles 를 HTML 에 inject 하는 mechanism". Next.js 13 App Router 의
useServerInsertedHTMLhook 도입 — 매 streaming RSC render 도중 styled-components / emotion / @mui 가 generated CSS 를<head>의 inject. 2026 zero-runtime CSS (Vanilla Extract / Panda) 의 등장 으로 registry 의 less common, but legacy SC/emotion app 의 still required.
매 핵심
매 Why needed
- CSS-in-JS = runtime styles: rules generated when component renders.
- SSR: server renders HTML; client hydrates. Without registry → FOUC (flash of unstyled content) + hydration mismatch.
- Streaming SSR: HTML chunks sent progressively. Styles must inject as components render, not at end.
- App Router:
useServerInsertedHTMLprovides hook into Suspense boundary stream.
매 Mechanism
- Server: collect styles into sheet during render (per request).
- Server: insert
<style>tags into HTML stream viauseServerInsertedHTML. - Client: hydrate — runtime takes over, no re-render needed.
- Concurrency: per-request sheet (no global state pollution).
매 응용
- styled-components in Next.js App Router.
- Emotion in Next.js App Router.
- @mui v5+ in Next.js.
💻 패턴
styled-components Registry
// app/lib/registry.tsx
'use client';
import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode;
}) {
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement();
styledComponentsStyleSheet.instance.clearTag();
return <>{styles}</>;
});
if (typeof window !== 'undefined') return <>{children}</>;
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
);
}
Apply in root layout
// app/layout.tsx
import StyledComponentsRegistry from './lib/registry';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
}
Emotion Registry
// app/lib/emotion-registry.tsx
'use client';
import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { useServerInsertedHTML } from 'next/navigation';
import { useState } from 'react';
export default function EmotionRegistry({
children,
}: {
children: React.ReactNode;
}) {
const [{ cache, flush }] = useState(() => {
const cache = createCache({ key: 'css' });
cache.compat = true;
const prevInsert = cache.insert;
let inserted: string[] = [];
cache.insert = (...args) => {
const serialized = args[1];
if (cache.inserted[serialized.name] === undefined) {
inserted.push(serialized.name);
}
return prevInsert(...args);
};
const flush = () => {
const prev = inserted;
inserted = [];
return prev;
};
return { cache, flush };
});
useServerInsertedHTML(() => {
const names = flush();
if (names.length === 0) return null;
let styles = '';
for (const name of names) {
styles += cache.inserted[name];
}
return (
<style
data-emotion={`${cache.key} ${names.join(' ')}`}
dangerouslySetInnerHTML={{ __html: styles }}
/>
);
});
return <CacheProvider value={cache}>{children}</CacheProvider>;
}
@mui Registry
// app/lib/mui-registry.tsx
'use client';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import { ThemeProvider } from '@mui/material/styles';
import { theme } from './theme';
export default function MuiRegistry({
children,
}: {
children: React.ReactNode;
}) {
return (
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</AppRouterCacheProvider>
);
}
Pages Router (legacy) — _document.tsx
// pages/_document.tsx — Pages Router uses different mechanism
import Document, { DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: [initialProps.styles, sheet.getStyleElement()],
};
} finally {
sheet.seal();
}
}
}
Verifying SSR works
// 1. View source (Cmd+U) — should see <style> tags with rules
// 2. Disable JS in DevTools — page should still be styled
// 3. Check Network tab — no FOUC during page transition
// 4. React DevTools — no hydration mismatch warnings
매 결정 기준
| 상황 | Approach |
|---|---|
| Greenfield 2026 Next.js | Tailwind / Vanilla Extract — no registry needed |
| Existing styled-components migration | Add StyledComponentsRegistry |
| Emotion-based codebase | EmotionRegistry pattern |
| @mui v5+ | AppRouterCacheProvider (built-in) |
| Pages Router legacy | _document.tsx + sheet collection |
기본값: greenfield → zero-runtime CSS (no registry). Existing CSS-in-JS → use library-recommended registry pattern.
🔗 Graph
- 부모: CSS in JS · SSR
- 변형: useServerInsertedHTML
- 응용: Styled Components v6
- Adjacent: Streaming SSR · React Server Components — 경계 의식 · Hydration
🤖 LLM 활용
언제: SSR setup for CSS-in-JS, Next.js App Router migration from Pages, debugging FOUC / hydration mismatch. 언제 X: Tailwind / CSS Modules / Vanilla Extract — these are zero-runtime, no registry needed.
❌ 안티패턴
- No registry → FOUC: styled-components SSR without registry shows unstyled HTML on first paint.
- Global sheet (not per-request): cross-request style pollution / memory leak.
'use client'on registry but rendered at top level: marks entire tree as client — kills RSC benefit. Wrap deeply.- Forgetting
clearTag(): duplicate styles inserted on each chunk. - Mixing registries: emotion + styled-components → two style systems, double bundle, conflicts.
🧪 검증 / 중복
- Verified (Next.js docs
app/building-your-application/styling, styled-components Next.js example, Emotion + Next.js guide). - 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full canonical (registry mechanism + SC/Emotion/MUI patterns + Pages Router fallback) |