chore(brain): ASTRA 성장 자산 동기화 — 기능 인벤토리·growth(약점프로필/학습큐)·일화기억·장기기억·회의록 원문

This commit is contained in:
2026-06-12 16:37:41 +09:00
parent a97fc7be07
commit 89fb05a28a
781 changed files with 138546 additions and 47 deletions
@@ -0,0 +1,161 @@
---
id: wiki-2026-0508-2026-04-25-skybound-skill-concep
title: Skybound — Skill Concept and Hangar Layout Overlap Fix
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Skybound Hangar, Skill Tree Layout]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [skybound, gamedev, ui-layout, skill-tree, hangar]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: phaser3
---
# Skybound — Skill Concept and Hangar Layout Overlap Fix
## 매 한 줄
> **"매 hangar UI 의 layout overlap 은 grid alignment + responsive scale 의 부재 의 symptom"**. 매 Skybound 의 hangar (meta-progression hub) 에서 airframe selector + skill tree + currency display 의 z-overlap 발생. 매 작업은 layout grid 재설계 + skill 의 conceptual taxonomy (active/passive/mod) 정립.
## 매 핵심
### 매 Skill 3 Taxonomy
- **Active**: cooldown-based, player-triggered (rare in VSL — only "ultimate" 등).
- **Passive**: always-on stat modifier (crit chance, hp regen 등).
- **Mod**: weapon-specific behavior change (split shot, pierce 등).
### 매 Hangar Layout 8-Region
1. Top-bar: currency + run timer.
2. Left rail: airframe carousel.
3. Center: selected airframe preview (3D-ish parallax).
4. Right panel: skill tree.
5. Bottom-bar: load-out summary + start button.
6. Modal layer: tooltip / detail.
7. Toast layer: notifications.
8. Background: parallax sky.
### 매 Overlap Fix Pattern
- 매 region 을 named slot 으로 declarative 의 정의.
- 매 child element 의 absolute positioning 의 X — slot-relative anchor.
- 매 z-index 의 layer 별 100 단위 의 spacing.
## 💻 패턴
### Slot-Based Layout
```typescript
type Slot = { x: number; y: number; w: number; h: number; z: number };
const HANGAR_SLOTS: Record<string, Slot> = {
topBar: { x: 0, y: 0, w: 1920, h: 80, z: 100 },
leftRail: { x: 0, y: 80, w: 240, h: 880, z: 100 },
center: { x: 240, y: 80, w: 1200, h: 800, z: 50 },
rightPanel: { x: 1440, y: 80, w: 480, h: 880, z: 100 },
bottomBar: { x: 240, y: 880, w: 1200, h: 200, z: 100 },
modal: { x: 0, y: 0, w: 1920, h: 1080, z: 500 },
toast: { x: 1620, y: 100, w: 280, h: 800, z: 600 },
};
function placeIn(slot: Slot, child: Phaser.GameObjects.Container, anchor: 'tl' | 'center' = 'tl') {
if (anchor === 'center') {
child.setPosition(slot.x + slot.w/2, slot.y + slot.h/2);
} else {
child.setPosition(slot.x, slot.y);
}
child.setDepth(slot.z);
}
```
### Skill Definition
```typescript
type Skill =
| { id: string; kind: 'active'; cooldownSec: number; effect: ActiveEffect }
| { id: string; kind: 'passive'; modifiers: StatModifier[] }
| { id: string; kind: 'mod'; targetWeapon: string; transform: WeaponTransform };
const SKILLS: Skill[] = [
{ id: 'overdrive', kind: 'active', cooldownSec: 60, effect: { kind: 'damage-boost', mul: 2, durationSec: 8 } },
{ id: 'iron-skin', kind: 'passive', modifiers: [{ stat: 'hp', kind: 'mul', value: 1.2 }] },
{ id: 'split-shot', kind: 'mod', targetWeapon: 'cannon', transform: { kind: 'multishot', count: 3, spreadDeg: 15 } },
];
```
### Skill Tree Render (overlap-free)
```typescript
class SkillTreeView extends Phaser.GameObjects.Container {
constructor(scene: Phaser.Scene, slot: Slot, nodes: SkillNode[]) {
super(scene);
placeIn(slot, this);
const cellW = slot.w / 4;
const cellH = 80;
nodes.forEach((node, i) => {
const col = i % 4;
const row = Math.floor(i / 4);
const sprite = scene.add.sprite(col * cellW + cellW/2, row * cellH + cellH/2, node.icon);
this.add(sprite);
});
}
}
```
### Tooltip on Hover (modal layer)
```typescript
function showTooltip(scene: Phaser.Scene, x: number, y: number, skill: Skill) {
const tt = scene.add.container(x + 16, y + 16);
tt.setDepth(HANGAR_SLOTS.modal.z + 10);
const bg = scene.add.rectangle(0, 0, 280, 120, 0x000000, 0.9).setOrigin(0);
const text = scene.add.text(8, 8, formatSkill(skill), { fontSize: '14px', wordWrap: { width: 264 } });
tt.add([bg, text]);
return tt;
}
```
### Responsive Rescale
```typescript
function rescaleForViewport(viewportW: number, viewportH: number) {
const targetW = 1920, targetH = 1080;
const scale = Math.min(viewportW / targetW, viewportH / targetH);
scene.cameras.main.setZoom(scale);
scene.cameras.main.centerOn(targetW/2, targetH/2);
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Many small UI elements | Slot grid + named anchor |
| Mobile + desktop | Responsive rescale (uniform zoom) |
| Tooltip clipping | Modal layer with z+10 boost |
| Skill tree large | Scrollable container in right panel |
**기본값**: 매 declarative slot grid + 100-unit z-tier + skill 3-kind union type.
## 🔗 Graph
- 부모: [[Skill-Tree-Design]]
- Adjacent: [[API 응답 및 상태 모델링 (State Modeling and API Responses)|Discriminated-Unions]]
## 🤖 LLM 활용
**언제**: layout slot enumeration, skill taxonomy brainstorm, tooltip copy.
**언제 X**: pixel-perfect alignment (manual eyeballing 필요).
## ❌ 안티패턴
- **Absolute pixel hardcode everywhere**: refactor 의 nightmare — slot indirection 의 사용.
- **Z-index ad-hoc**: overlap bug 재발 — layer tier (100/200/500/600) 의 사용.
- **Skill kind 분리 안함**: switch 폭발 — discriminated union 의 사용.
- **Tooltip in same layer**: clip — modal layer 의 분리.
## 🧪 검증 / 중복
- Verified (Phaser 3 docs, game UI postmortems).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — slot grid + skill taxonomy + overlap fix 정리 |
@@ -0,0 +1,175 @@
---
id: wiki-2026-0508-2026-04-26-skybound-skip-upgrade
title: Skybound — Skip Upgrade and Weapon Transform Reconfiguration
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Skip Upgrade Mechanic, Weapon Transform]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [skybound, gamedev, upgrade-system, weapon-transform, choice-design]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: phaser3
---
# Skybound — Skip Upgrade and Weapon Transform Reconfiguration
## 매 한 줄
> **"매 level-up choice 의 design 은 'skip option' + 'weapon transform' 의 조합 으로 build path agency 의 maximize"**. 매 Skybound 의 4-26 작업은 standard 3-card upgrade 위에 skip (small reward) + weapon transform (2 weapons → 1 evolved) 의 mechanic 추가, evolution graph 의 reconfiguration.
## 매 핵심
### 매 Choice Mechanics
- **Standard Pick**: 3 random choice 중 1 — basic loop.
- **Skip**: choice reject + small XP/gold/heal — bad-RNG escape valve.
- **Reroll**: 1 reroll/level (limited stock) — guided RNG.
- **Banish**: remove option from future pool — long-term build steering.
- **Transform**: 2 specific weapons + passive prereq → evolved weapon.
### 매 Weapon Transform Triggers
- 매 transform 은 (weapon A maxed) + (passive B owned) → weapon A becomes evolved A'.
- 매 evolved weapon 의 max level 의 X — terminal form.
- 매 transform 시 base weapon slot 의 freed (원래 의 기획 — 4-26 변경 후 slot 의 retained).
### 매 Reconfiguration (4-26)
- 매 transform 후 base weapon slot 의 retained — multi-evolve build viability.
- 매 evolved weapon 에 cosmetic visual 차별화 강화.
- 매 transform notification 의 full-screen freeze + audio sting.
## 💻 패턴
### Upgrade Choice Generator
```typescript
type Choice =
| { kind: 'weapon'; weaponId: string; toLevel: number }
| { kind: 'passive'; passiveId: string; toLevel: number }
| { kind: 'skip'; reward: SkipReward };
function generateChoices(player: Player, pool: UpgradePool, count = 3): Choice[] {
const eligible = pool.filter(u => isEligible(u, player) && !player.banished.has(u.id));
const picks = sample(eligible, count);
return picks.map(p => toChoice(p, player));
}
function rollSkip(): Choice {
const r = Math.random();
if (r < 0.5) return { kind: 'skip', reward: { kind: 'gold', amount: 50 } };
if (r < 0.8) return { kind: 'skip', reward: { kind: 'xp', amount: 20 } };
return { kind: 'skip', reward: { kind: 'heal', percent: 0.1 } };
}
```
### Weapon Transform Rules
```typescript
type TransformRule = {
weaponId: string;
passiveId: string;
evolvedWeaponId: string;
};
const TRANSFORM_RULES: TransformRule[] = [
{ weaponId: 'cannon', passiveId: 'gunpowder', evolvedWeaponId: 'mega-cannon' },
{ weaponId: 'missile', passiveId: 'targeting', evolvedWeaponId: 'swarm-missile' },
{ weaponId: 'flak', passiveId: 'shrapnel', evolvedWeaponId: 'meteor-flak' },
];
function checkTransforms(player: Player): TransformRule[] {
return TRANSFORM_RULES.filter(rule => {
const w = player.weapons.find(w => w.id === rule.weaponId);
const p = player.passives.find(p => p.id === rule.passiveId);
return w && w.level >= w.maxLevel && p && p.level >= 1 && !player.weapons.some(w => w.id === rule.evolvedWeaponId);
});
}
```
### Apply Transform (4-26 reconfig — slot retained)
```typescript
function applyTransform(player: Player, rule: TransformRule, scene: Phaser.Scene): void {
// Old behavior: remove base weapon. New (4-26): keep base.
const evolved = createWeapon(rule.evolvedWeaponId);
player.weapons.push(evolved); // base weapon retained in its slot
scene.scene.pause();
scene.add.image(W/2, H/2, `evolved-${rule.evolvedWeaponId}-banner`).setDepth(1000);
scene.sound.play('transform-sting');
scene.time.delayedCall(2500, () => scene.scene.resume());
}
```
### Banish + Reroll Stock
```typescript
class ChoiceMeta {
rerollStock = 1;
banishStock = 0;
reroll(level: number): Choice[] | null {
if (this.rerollStock <= 0) return null;
this.rerollStock--;
return generateChoices(player, pool);
}
banish(player: Player, choice: Choice): boolean {
if (this.banishStock <= 0) return false;
this.banishStock--;
if (choice.kind !== 'skip') player.banished.add(idOf(choice));
return true;
}
}
```
### Choice UI (with skip card)
```typescript
class LevelUpModal extends Phaser.GameObjects.Container {
constructor(scene, choices: Choice[], onPick: (c: Choice) => void) {
super(scene);
choices.forEach((c, i) => {
const card = makeCard(scene, c).setPosition(i * 320, 0).setInteractive();
card.on('pointerdown', () => onPick(c));
this.add(card);
});
const skipCard = makeCard(scene, rollSkip()).setPosition(choices.length * 320, 0).setInteractive();
skipCard.on('pointerdown', () => onPick(rollSkip()));
this.add(skipCard);
}
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| RNG too punishing | Skip + reroll + banish triumvirate |
| Build path 의 monotony | Transform with prereq passive |
| Slot pressure | 4-26 reconfig: transform retains slot |
| New player overwhelm | Tutorial: introduce mechanics 1-by-1 |
**기본값**: 매 3-card + skip + 1 reroll + transform-retains-slot.
## 🔗 Graph
- 부모: [[Choice-Architecture]]
- Adjacent: [[API 응답 및 상태 모델링 (State Modeling and API Responses)|Discriminated-Unions]]
## 🤖 LLM 활용
**언제**: weapon evolution chart, prereq passive brainstorm, skip reward tuning.
**언제 X**: visual transform animation timing.
## ❌ 안티패턴
- **No skip option**: bad-roll lockup — skip 의 always available.
- **Transform removes slot**: build flexibility 망 — 4-26 reconfig 의 사용.
- **Reroll unlimited**: tension 망 — stock-based.
- **Evolved weapon levelable**: power creep — terminal form 의 lock.
## 🧪 검증 / 중복
- Verified (Vampire Survivors evolution mechanic, Brotato shop).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — skip + transform reconfig (slot retained) 정리 |
@@ -0,0 +1,174 @@
---
id: wiki-2026-0508-2026-04-26-skybound-stage-minibo
title: Skybound — Stage Miniboss Pattern Differentiation
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Miniboss Patterns, Stage-Specific Miniboss]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [skybound, gamedev, miniboss-design, attack-pattern, stage-identity]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: phaser3
---
# Skybound — Stage Miniboss Pattern Differentiation
## 매 한 줄
> **"매 8 stage 의 miniboss 가 'pattern signature' 의 distinct 일 때 stage identity 의 안 collapse"**. 매 Skybound 의 4-26 작업은 stage 별 miniboss 의 attack pattern (radial / sweep / charge / split / chain / mirror / summoner / null-zone) 의 1:1 mapping 정립.
## 매 핵심
### 매 Stage-to-Pattern Mapping
1. **Stage 1**: Radial — 8-direction burst, slow turn rate.
2. **Stage 2**: Sweep — laser arc, telegraphed.
3. **Stage 3**: Charge — dash line + recover stun.
4. **Stage 4**: Split — projectile fragments mid-flight.
5. **Stage 5**: Chain — bounce projectiles between mobs.
6. **Stage 6**: Mirror — clones player movement (lagged).
7. **Stage 7**: Summoner — spawns minions in waves.
8. **Stage 8**: Null-zone — area denial circles, forces movement.
### 매 Differentiation Rules
- 매 pattern 의 unique tell (visual + audio).
- 매 pattern 별 counter-play (positioning / timing / kiting).
- 매 stage progression 의 pattern complexity escalation.
### 매 Phase Variants
- 매 miniboss HP < 50% 시 pattern 의 modifier 추가 (faster, more, larger).
- 매 enrage (HP < 20%) 의 mixed pattern.
## 💻 패턴
### Pattern Definition
```typescript
type AttackPattern =
| { kind: 'radial'; bullets: number; bulletSpeed: number; rotateRate: number }
| { kind: 'sweep'; arcDeg: number; durationMs: number; telegraphMs: number }
| { kind: 'charge'; chargeSpeed: number; recoverMs: number }
| { kind: 'split'; firstStage: { count: number; speed: number }; splitAt: number; splitInto: number }
| { kind: 'chain'; bounces: number; targets: 'enemies' | 'walls' }
| { kind: 'mirror'; lagMs: number }
| { kind: 'summoner'; minionId: string; perWave: number; everyMs: number }
| { kind: 'null-zone'; radius: number; lifespanMs: number; spawnEveryMs: number };
const STAGE_PATTERNS: Record<number, AttackPattern> = {
1: { kind: 'radial', bullets: 8, bulletSpeed: 200, rotateRate: 0.5 },
2: { kind: 'sweep', arcDeg: 120, durationMs: 1500, telegraphMs: 600 },
3: { kind: 'charge', chargeSpeed: 600, recoverMs: 1200 },
4: { kind: 'split', firstStage: { count: 3, speed: 240 }, splitAt: 0.5, splitInto: 5 },
5: { kind: 'chain', bounces: 3, targets: 'enemies' },
6: { kind: 'mirror', lagMs: 600 },
7: { kind: 'summoner', minionId: 'drone-small', perWave: 4, everyMs: 4000 },
8: { kind: 'null-zone', radius: 120, lifespanMs: 5000, spawnEveryMs: 2500 },
};
```
### Pattern Runner
```typescript
class MinibossController {
pattern: AttackPattern;
phase: 'p1' | 'enrage' = 'p1';
update(dt: number, hp: number, maxHp: number) {
if (hp / maxHp < 0.2) this.phase = 'enrage';
const speedMul = this.phase === 'enrage' ? 1.6 : 1;
this.runPattern(this.pattern, dt, speedMul);
}
private runPattern(p: AttackPattern, dt: number, mul: number) {
switch (p.kind) {
case 'radial': return this.runRadial(p, dt, mul);
case 'sweep': return this.runSweep(p, dt, mul);
case 'charge': return this.runCharge(p, dt, mul);
case 'split': return this.runSplit(p, dt, mul);
case 'chain': return this.runChain(p, dt, mul);
case 'mirror': return this.runMirror(p, dt, mul);
case 'summoner':return this.runSummoner(p, dt, mul);
case 'null-zone': return this.runNullZone(p, dt, mul);
}
}
}
```
### Radial Volley
```typescript
private runRadial(p: AttackPattern & { kind: 'radial' }, dt: number, mul: number) {
this.cooldown -= dt;
if (this.cooldown > 0) return;
this.cooldown = 1.2 / mul;
for (let i = 0; i < p.bullets; i++) {
const angle = (i / p.bullets) * Math.PI * 2 + this.aimAngle;
spawnProjectile(this.pos, angle, p.bulletSpeed * mul);
}
this.aimAngle += p.rotateRate * dt;
}
```
### Mirror (lagged player clone)
```typescript
private runMirror(p: AttackPattern & { kind: 'mirror' }, _dt: number, _mul: number) {
const samples = this.playerHistory.samplesOlderThan(p.lagMs);
const target = samples.lastBefore(p.lagMs);
if (target) {
this.moveToward(target.pos, this.speed);
if (this.fireCooldown <= 0) {
spawnProjectileAt(this.pos, target.pos.angleFrom(this.pos), 320);
this.fireCooldown = 0.8;
}
}
}
```
### Null-Zone Spawning
```typescript
private runNullZone(p: AttackPattern & { kind: 'null-zone' }, _dt: number, mul: number) {
this.zoneCooldown -= _dt;
if (this.zoneCooldown > 0) return;
this.zoneCooldown = (p.spawnEveryMs / 1000) / mul;
spawnNullZone({
pos: this.playerPosOffsetRandom(200),
radius: p.radius,
lifespanMs: p.lifespanMs,
});
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Patterns feel samey | 1 stage = 1 signature pattern |
| Late-stage too easy | Enrage modifier (1.5-2x speed) |
| Pattern unreadable | Telegraph + audio sting |
| Mirror too punishing | Lag 600ms+ (player can outrun) |
**기본값**: 매 8-pattern enum, stage:pattern = 1:1, enrage 1.6x speed mul.
## 🔗 Graph
- Adjacent: [[API 응답 및 상태 모델링 (State Modeling and API Responses)|Discriminated-Unions]]
## 🤖 LLM 활용
**언제**: pattern enumeration, counter-play brainstorm, telegraph copy.
**언제 X**: hitbox tuning, frame-perfect timing.
## ❌ 안티패턴
- **All bosses radial**: identity collapse — 1:1 pattern.
- **No enrage phase**: predictable end — 20% HP threshold modifier.
- **Mirror lag too short**: unbeatable — 600ms+ lag.
- **Telegraph 누락**: unfair — visual+audio always.
## 🧪 검증 / 중복
- Verified (Touhou pattern taxonomy, Hades boss design).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — 8-pattern stage:miniboss mapping 정리 |
@@ -0,0 +1,181 @@
---
id: wiki-2026-0508-branded-types-for-nominal-typing
title: Branded Types for Nominal Typing
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Nominal Types TypeScript, Opaque Types, Branded Types]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, types, nominal-typing, type-safety]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: TS 5.x
---
# Branded Types for Nominal Typing
## 매 한 줄
> **"매 string 매 매 string 의 X"**. Branded types는 TypeScript의 structural type system 안에 nominal distinction을 흉내내는 trick — 동일한 underlying type (예: string)을 `UserId` vs `OrderId`처럼 incompatible 하게 만들어 mix-up을 compile time에 잡는다. 2026 거의 모든 fintech / id-heavy domain code에 standard.
## 매 핵심
### 매 동기
- TS는 structural — `{ name: string }` 두 개는 호환.
- Domain-specific id (UserId, OrderId, Email) 가 모두 plain string → 매 swap mistake 매 compile O.
- Brand 추가 → structurally distinct.
### 매 구현 패턴
- **Symbol-based brand**: `string & { readonly [BrandKey]: 'UserId' }`.
- **Intersection brand**: simple intersection.
- **TypeScript 5.x `unique symbol`**: 매 compile-only field — runtime cost zero.
### 매 응용
1. ID 매 strong typing (UserId vs OrderId vs SessionId).
2. Validated string (Email, URL) — runtime check 후 brand.
3. Unit type (Meter, Second) — physical unit safety.
## 💻 패턴
### Basic brand utility
```ts
declare const __brand: unique symbol;
type Brand<T, B> = T & { readonly [__brand]: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function userId(s: string): UserId { return s as UserId; }
function orderId(s: string): OrderId { return s as OrderId; }
function getUser(id: UserId) { /* ... */ }
const u = userId('u_123');
const o = orderId('o_456');
getUser(u); // OK
getUser(o); // Error: OrderId not assignable to UserId
getUser('u_123'); // Error: string not assignable to UserId
```
### Branded with validation (smart constructor)
```ts
type Email = Brand<string, 'Email'>;
function parseEmail(s: string): Email | null {
return /^[^@]+@[^@]+\.[^@]+$/.test(s) ? (s as Email) : null;
}
function sendMail(to: Email, subj: string) { /* ... */ }
const e = parseEmail('a@b.com');
if (e) sendMail(e, 'Hi'); // 매 narrowed Email
```
### Unit-of-measure brand
```ts
type Meter = Brand<number, 'Meter'>;
type Second = Brand<number, 'Second'>;
type MeterPerSecond = Brand<number, 'm/s'>;
function speed(d: Meter, t: Second): MeterPerSecond {
return (d / t) as MeterPerSecond;
}
const d = 100 as Meter;
const t = 9.58 as Second;
const v = speed(d, t);
// speed(t, d) — Error: Second not assignable to Meter
```
### Branded primitive with helper
```ts
function makeBrand<B extends string>() {
return {
of<T>(v: T): Brand<T, B> { return v as Brand<T, B>; },
is(_v: unknown): _v is Brand<unknown, B> { return true; }
};
}
const UserId = makeBrand<'UserId'>();
const OrderId = makeBrand<'OrderId'>();
const id = UserId.of('u_1');
```
### Zod integration
```ts
import { z } from 'zod';
const UserIdSchema = z.string().regex(/^u_/).brand<'UserId'>();
type UserId = z.infer<typeof UserIdSchema>;
function fetchUser(id: UserId) { /* ... */ }
const parsed = UserIdSchema.parse(req.params.id);
fetchUser(parsed);
```
### Discriminated brand for state machine
```ts
type Pending = Brand<{ status: 'pending'; id: string }, 'Pending'>;
type Approved = Brand<{ status: 'approved'; id: string; by: string }, 'Approved'>;
type Rejected = Brand<{ status: 'rejected'; id: string; reason: string }, 'Rejected'>;
type Order = Pending | Approved | Rejected;
function approve(o: Pending, by: string): Approved {
return { ...o, status: 'approved', by } as Approved;
}
```
### Pattern: opaque type with module
```ts
// userId.ts
declare const brand: unique symbol;
export type UserId = string & { readonly [brand]: 'UserId' };
export const UserId = (s: string): UserId => {
if (!s.startsWith('u_')) throw new Error('invalid');
return s as UserId;
};
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| ID type collision risk | Brand 매 essential |
| Internal-only domain | Brand optional |
| Runtime validation needed | Smart constructor + brand |
| Schema parsing (Zod/Effect) | `.brand<'Name'>()` |
| Unit safety | Brand on number |
**기본값**: external boundary (DB, API) 에 entry point 에서 brand, internal logic은 branded type만 받음.
## 🔗 Graph
- 부모: [[TypeScript]] · [[TypeScript 타입 시스템 (TypeScript Type System)|Type System]]
- 변형: [[Opaque Types]]
- Adjacent: [[Zod]] · [[Effect-TS]]
## 🤖 LLM 활용
**언제**: domain model schema generation, brand utility scaffolding, smart constructor pattern.
**언제 X**: runtime brand check 매 X (compile-only) — runtime 필요 시 class / Symbol.
## ❌ 안티패턴
- **Brand 추가 후 무한 cast**: `as UserId` 남발 → safety 매 lost. Smart constructor 사용.
- **Brand on every type**: 매 friction 매 high — 매 boundary type 만.
- **Brand with mutable object**: 매 객체 매 변형 후 brand mismatch — readonly 사용.
- **Non-unique brand key**: `'id'` — collision risk. unique symbol 사용.
## 🧪 검증 / 중복
- Verified (Effect-TS docs, Zod 3.x, TypeScript handbook).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — branded type pattern + Zod integration |
@@ -0,0 +1,190 @@
---
id: wiki-2026-0508-buffer-allocation
title: Buffer Allocation
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [ArrayBuffer Allocation, Typed Array Pool]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [performance, memory, buffer, typed-array, gc]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript/TypeScript
framework: Browser/Node/WebGPU
---
# Buffer Allocation
## 매 한 줄
> **"매 buffer 매 reuse 매 GC 의 X"**. Buffer allocation은 binary data buffer (ArrayBuffer / Typed Array)의 effective lifecycle 관리 — naive `new Uint8Array(N)` per operation은 GC churn을 유발해 frame budget을 깬다. 2026 WebGPU / WebCodecs / canvas heavy app 의 must-skill.
## 매 핵심
### 매 cost source
- **Allocation**: V8/SpiderMonkey backing-store malloc + zero-fill.
- **GC**: large allocation → Old-gen → expensive sweep.
- **Cache miss**: 매 fresh memory 의 cold cache.
- **Fragmentation**: many sizes → heap 의 fragmented.
### 매 strategy
- **Pool / freelist**: 매 size class 매 reuse.
- **Subarray view**: single big buffer + slice views.
- **Pre-allocation**: peak size 부터 allocate.
- **SharedArrayBuffer**: cross-thread, no copy.
### 매 응용
1. Audio / video frame buffer.
2. WebGL / WebGPU vertex / index buffer.
3. WebSocket binary message parser.
## 💻 패턴
### Simple buffer pool
```ts
class BufferPool {
private pool: Uint8Array[] = [];
constructor(private size: number, private max = 32) {}
acquire(): Uint8Array {
return this.pool.pop() ?? new Uint8Array(this.size);
}
release(buf: Uint8Array) {
if (this.pool.length < this.max) {
buf.fill(0);
this.pool.push(buf);
}
}
}
const pool = new BufferPool(4096);
const b = pool.acquire();
// use ...
pool.release(b);
```
### Size-class pool
```ts
class SizeClassPool {
private classes = new Map<number, Uint8Array[]>();
private classOf(n: number): number {
return Math.pow(2, Math.ceil(Math.log2(Math.max(n, 16))));
}
acquire(n: number): Uint8Array {
const cls = this.classOf(n);
const list = this.classes.get(cls);
return (list?.pop() ?? new Uint8Array(cls)).subarray(0, n);
}
release(buf: Uint8Array) {
const cls = buf.buffer.byteLength;
if (!this.classes.has(cls)) this.classes.set(cls, []);
this.classes.get(cls)!.push(new Uint8Array(buf.buffer));
}
}
```
### Single backing buffer + views
```ts
const big = new ArrayBuffer(1024 * 1024); // 1MB
const headers = new Uint32Array(big, 0, 256); // 1KB header
const body = new Uint8Array(big, 1024, 1024 * 1023); // remainder
```
### WebSocket binary parsing — no copy
```ts
ws.binaryType = 'arraybuffer';
ws.onmessage = (e: MessageEvent<ArrayBuffer>) => {
const dv = new DataView(e.data);
const type = dv.getUint8(0);
const length = dv.getUint32(1, true);
const payload = new Uint8Array(e.data, 5, length);
handle(type, payload);
};
```
### SharedArrayBuffer ring buffer (worker comms)
```ts
// main
const sab = new SharedArrayBuffer(1024);
const view = new Int32Array(sab);
worker.postMessage(sab);
// worker — Atomics 매 lock-free synchronization
self.onmessage = (e) => {
const view = new Int32Array(e.data);
Atomics.store(view, 0, 42);
Atomics.notify(view, 0, 1);
};
```
### Reusable canvas pixel buffer
```ts
class FrameRecycler {
private buffers: ImageData[] = [];
acquire(w: number, h: number) {
return this.buffers.find(b => b.width === w && b.height === h)
?? new ImageData(w, h);
}
release(b: ImageData) {
if (this.buffers.length < 4) this.buffers.push(b);
}
}
```
### Detecting allocation hot spots
```ts
// 매 dev-only — 매 wrap allocator 매 trace
const _orig = Uint8Array;
let count = 0;
(globalThis as any).Uint8Array = function (...args: any[]) {
count++;
if (count % 1000 === 0) console.warn('Uint8Array allocations:', count);
return new _orig(...args);
};
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Per-frame temp buffer | pool with size class |
| Streaming binary protocol | preallocated parser buffer |
| Cross-thread share | SharedArrayBuffer + Atomics |
| GPU upload | persistent GPU buffer + map/unmap |
| Rare large alloc | direct allocate |
**기본값**: measure GC pressure first; pool only when allocator shows up in profile.
## 🔗 Graph
- 부모: [[Memory Management]] · [[Performance]]
- 변형: [[Object Pool]]
- 응용: [[WebGPU]]
- Adjacent: [[SharedArrayBuffer]] · [[Typed Array]] · [[Garbage Collection]]
## 🤖 LLM 활용
**언제**: pool implementation scaffold, view layout calculation, ring buffer design.
**언제 X**: 매 GC actual measurement — Chrome Memory profiler.
## ❌ 안티패턴
- **Premature pool**: 매 micro-opt — measure first.
- **Pool 무제한**: 매 leak — max size cap.
- **Subarray after detach**: transferred buffer — invalid.
- **Concurrent writes without Atomics**: SharedArrayBuffer 매 race.
- **Forget to zero-fill on release**: 매 stale data leak across owners.
## 🧪 검증 / 중복
- Verified (V8 blog, MDN ArrayBuffer, WebGPU spec).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — buffer pool + view layout pattern |
@@ -0,0 +1,187 @@
---
id: wiki-2026-0508-bundle-size-optimization
title: Bundle Size Optimization
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [JS Bundle Optimization, Web Bundle Reduction]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [bundle, performance, webpack, vite, tree-shaking]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript/TypeScript
framework: Vite/Rollup/Webpack
---
# Bundle Size Optimization
## 매 한 줄
> **"매 byte 매 less 매 user time 매 less"**. Bundle size optimization은 production JS/CSS payload를 줄여 LCP/INP/TBT 개선 + mobile-first user 의 perceived speed 개선. 2026 standard tooling: Vite + Rollup tree-shaking, modern bundle analysis (Bundle Buddy, esbuild-visualizer), bundle budgets enforcement.
## 매 핵심
### 매 4 lever
- **Tree shaking**: ESM only, sideEffects:false, no re-export wildcards.
- **Code splitting**: route / component lazy import.
- **Compression**: brotli > gzip; precompress at build.
- **Dependency surgery**: heavy lib → lighter alt or self-implement.
### 매 측정 우선
- Bundle visualizer (rollup-plugin-visualizer, source-map-explorer).
- Bundle budget in CI (e.g., size-limit, bundlesize).
- Real device testing (slow 3G profile).
### 매 응용
1. Lazy-load route chunks.
2. Remove unused locales (date-fns, moment).
3. Replace lodash with native / lodash-es.
## 💻 패턴
### Vite + visualizer
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({ filename: 'stats.html', gzipSize: true, brotliSize: true })
],
build: {
rollupOptions: {
output: {
manualChunks: {
react: ['react', 'react-dom'],
vendor: ['date-fns', 'zustand']
}
}
}
}
});
```
### Route-level code split (React)
```tsx
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
<Suspense fallback={<Skeleton />}>
<Dashboard />
</Suspense>
```
### Dynamic import for rare path
```ts
async function exportToPDF(data: Item[]) {
const { jsPDF } = await import('jspdf');
const doc = new jsPDF();
doc.text(JSON.stringify(data), 10, 10);
doc.save('out.pdf');
}
```
### Replace heavy lib
```ts
// X moment (~290KB)
import moment from 'moment';
moment().format('YYYY-MM-DD');
// O Intl (built-in, 0KB)
new Intl.DateTimeFormat('en-CA').format(new Date());
// O date-fns/format (tree-shakeable, ~3KB)
import { format } from 'date-fns/format';
format(new Date(), 'yyyy-MM-dd');
```
### sideEffects flag
```json
// package.json — library author 측
{
"name": "my-lib",
"type": "module",
"sideEffects": false,
"exports": {
".": {
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
}
}
```
### size-limit budget enforcement
```json
// package.json
{
"scripts": {
"size": "size-limit"
},
"size-limit": [
{ "path": "dist/index.js", "limit": "50 KB" },
{ "path": "dist/vendor.js", "limit": "120 KB" }
]
}
```
### Compression at build (brotli)
```ts
import compression from 'vite-plugin-compression2';
export default defineConfig({
plugins: [
compression({ algorithm: 'brotliCompress', exclude: [/\.(br)$/, /\.(gz)$/] })
]
});
```
### Server: strip locales from dayjs
```ts
import dayjs from 'dayjs';
import 'dayjs/locale/ko'; // 매 필요한 것만
dayjs.locale('ko');
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Initial bundle > 200KB | route split + lazy load |
| Single heavy lib | replace 또는 dynamic import |
| Multi-tenant build | per-tenant treeshake config |
| Library publish | ESM + `sideEffects:false` |
| Edge runtime | bundle ≤ 1MB 가까이 strict budget |
**기본값**: measure first → split → compress → swap heavy deps.
## 🔗 Graph
- 부모: [[Frontend Performance]]
- 변형: [[Code Splitting]]
- 응용: [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]] · [[LCP]]
- Adjacent: [[Vite]] · [[Rollup]] · [[esbuild]]
## 🤖 LLM 활용
**언제**: webpack/vite config audit, lib alternative suggestion, bundle analyzer interpretation.
**언제 X**: 매 production 매 swap deploy — actual measurement 필수.
## ❌ 안티패턴
- **CommonJS lib import**: tree shaking blocked — ESM 사용.
- **`import * as foo`**: bundler 매 mark 매 모든 export used.
- **Polyfill 전체**: target browser baseline + browserslist으로 narrow.
- **Single chunk all**: SPA → 매 long initial — split per route.
- **Dev source maps in prod**: ship source map only via separate URL or skip.
## 🧪 검증 / 중복
- Verified (web.dev bundle size guide, Vite docs, size-limit GitHub).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — bundle optim 4 lever + Vite/size-limit pattern |
@@ -0,0 +1,190 @@
---
id: wiki-2026-0508-cpu-overhead
title: CPU Overhead
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [CPU Cost, JS Main Thread Cost]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [performance, cpu, main-thread, profiling]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript
framework: Browser/Node
---
# CPU Overhead
## 매 한 줄
> **"매 main thread 매 free 매 fast UI"**. CPU overhead는 JS execution / parsing / hydration / re-render에 소비되는 main-thread time — 이게 길어지면 INP가 무너지고 user input이 lag한다. 2026 INP가 LCP를 대체한 third Core Web Vital이 되어 CPU profile + scheduling이 frontend 의 first concern.
## 매 핵심
### 매 source
- **Parse + compile**: download된 JS bytes → AST → bytecode (V8 측정 시 KB 당 ~1ms low-end mobile).
- **Hydration**: SSR HTML 위 React/Vue 매 attach.
- **Re-render**: state change → diff → DOM commit.
- **Long task** (>50ms): block input.
### 매 측정
- Chrome Performance panel — flame chart, "Long tasks".
- `PerformanceObserver` API on `longtask`.
- React Profiler / Vue Devtools timeline.
- Web Vitals — INP, TBT.
### 매 응용
1. JS payload 줄이기 → less parse.
2. Hydration partial / streaming.
3. Heavy work → Web Worker / requestIdleCallback / startTransition.
## 💻 패턴
### Long task observer
```ts
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn(`Long task: ${entry.duration.toFixed(0)}ms`, entry);
}
}
});
obs.observe({ type: 'longtask', buffered: true });
```
### Yield to main thread
```ts
function yieldToMain() {
return new Promise(resolve => setTimeout(resolve, 0));
}
async function processChunks(items: Item[]) {
for (let i = 0; i < items.length; i++) {
process(items[i]);
if (i % 100 === 0) await yieldToMain();
}
}
```
### scheduler.yield (Chrome 129+)
```ts
async function processBig(items: Item[]) {
for (const item of items) {
process(item);
if ('scheduler' in window && 'yield' in (window as any).scheduler) {
await (window as any).scheduler.yield();
}
}
}
```
### Web Worker offload
```ts
// worker.ts
self.onmessage = (e) => {
const result = heavyTransform(e.data);
self.postMessage(result);
};
// main.ts
const w = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
w.postMessage(largeData);
w.onmessage = (e) => render(e.data);
```
### React startTransition
```tsx
import { startTransition, useState } from 'react';
function Search() {
const [q, setQ] = useState('');
const [results, setResults] = useState<Item[]>([]);
function onChange(e) {
setQ(e.target.value); // urgent
startTransition(() => {
setResults(filter(allItems, e.target.value)); // background
});
}
return <input value={q} onChange={onChange} />;
}
```
### useDeferredValue
```tsx
function Page({ filter }) {
const deferredFilter = useDeferredValue(filter);
const items = useMemo(() => filterBig(deferredFilter), [deferredFilter]);
return <List items={items} />;
}
```
### requestIdleCallback for non-critical
```ts
const work = [...];
function schedule() {
if (!work.length) return;
requestIdleCallback((deadline) => {
while (work.length && deadline.timeRemaining() > 0) {
doOne(work.shift());
}
schedule();
});
}
schedule();
```
### Avoid layout thrash
```ts
// X — 매 read after write 매 force reflow
els.forEach(el => {
el.style.width = '100px';
console.log(el.offsetWidth); // forced sync layout
});
// O — 매 batch read, batch write
const widths = els.map(el => el.offsetWidth);
els.forEach((el, i) => el.style.width = widths[i] + 1 + 'px');
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Heavy compute (parse/transform) | Web Worker |
| Long list render | virtualization (TanStack Virtual) |
| Filter on input | useDeferredValue / startTransition |
| Background prefetch | requestIdleCallback |
| Animation | CSS / RAF, no JS-driven layout |
**기본값**: measure → smallest fix → re-measure.
## 🔗 Graph
- 부모: [[Frontend Performance]] · [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]]
- 변형: [[INP]]
- 응용: [[Web Worker]] · [[Concurrent Features|Concurrent Rendering]]
- Adjacent: [[Bundle Size Optimization]] · [[Hydration]]
## 🤖 LLM 활용
**언제**: long task identification, scheduler API generation, INP debug script.
**언제 X**: real device profiling — DevTools / WebPageTest 필수.
## ❌ 안티패턴
- **JS-driven animation**: setInterval + style 변경 — RAF / CSS 사용.
- **Sync XMLHttpRequest**: 매 main block — fetch async 사용.
- **Force layout in loop**: read after write — batch.
- **Hydration of static page**: islands / partial hydration.
- **Massive context provider**: 매 모든 child re-render — split context.
## 🧪 검증 / 중복
- Verified (web.dev INP guide, Chrome scheduler API, React 19 docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — CPU overhead pattern + scheduler API |
@@ -0,0 +1,175 @@
---
id: wiki-2026-0508-client-components
title: Client Components
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [RSC Client Components, "use client"]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, nextjs, rsc, frontend, hydration]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react/nextjs
---
# Client Components
## 매 한 줄
> **"매 interactive boundary"**. Client Components 매 React Server Components (RSC) 매 architecture 의 매 interactive half — `'use client'` directive 매 매 module-level boundary marker, 매 hydration + state + browser API 매 가능한 영역. 매 2026 현재 Next.js 13+ App Router / Remix Single Fetch / TanStack Start 의 default model.
## 매 핵심
### 매 boundary model
- `'use client'` 매 file-top directive — 매 module 부터 dependent tree 매 client bundle 에 포함.
- Server Component (default) 매 server-only render — 매 zero JS shipped.
- Client Component 매 hydrate — `useState` / `useEffect` / event handler / browser API 매 가능.
### 매 핵심 properties
- **Composability**: Server → Client 매 OK (props 통해), Client → Server 매 NOT (children prop slot 만 OK).
- **Serialization**: Server → Client props 매 serializable 만 (no functions, classes, Date OK via 2026 RSC payload).
- **Bundle**: 매 leaf client component 만 ship — 매 root 에서 'use client' 매 X.
### 매 응용
1. Form 매 controlled input + validation.
2. Animation / transition (Framer Motion, View Transitions API).
3. Browser API (geolocation, clipboard, IndexedDB).
4. Real-time (WebSocket, SSE consumer).
## 💻 패턴
### Basic client component
```tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>Count: {n}</button>;
}
```
### Server Component → Client Component (props)
```tsx
// page.tsx (Server)
import { getProducts } from '@/lib/db';
import { ProductGrid } from './ProductGrid';
export default async function Page() {
const products = await getProducts(); // server fetch
return <ProductGrid initial={products} />;
}
```
```tsx
// ProductGrid.tsx (Client — interactive filter)
'use client';
import { useState } from 'react';
export function ProductGrid({ initial }: { initial: Product[] }) {
const [filter, setFilter] = useState('');
const visible = initial.filter(p => p.name.includes(filter));
return (
<>
<input value={filter} onChange={e => setFilter(e.target.value)} />
{visible.map(p => <Card key={p.id} {...p} />)}
</>
);
}
```
### Client Component 안 Server Component (children slot)
```tsx
// Layout.tsx (Client — needs onClick)
'use client';
export function Sidebar({ children }: { children: ReactNode }) {
return <aside onClick={...}>{children}</aside>;
}
// page.tsx (Server)
import { Sidebar } from './Sidebar';
import { ServerProfile } from './ServerProfile';
export default function Page() {
return (
<Sidebar>
<ServerProfile /> {/* Server component as children — OK */}
</Sidebar>
);
}
```
### Server Action 호출 (Client → Server mutation)
```tsx
'use client';
import { createPost } from './actions'; // 'use server' file
export function NewPostForm() {
return (
<form action={createPost}>
<input name="title" />
<button>Submit</button>
</form>
);
}
```
### Suspense + Streaming
```tsx
// page.tsx (Server)
import { Suspense } from 'react';
import { Comments } from './Comments';
export default function Page() {
return (
<>
<Article />
<Suspense fallback={<Skeleton />}>
<Comments /> {/* streamed in */}
</Suspense>
</>
);
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Static rendering, data fetching | **Server Component** (default) |
| State / event / effect / browser API | **Client Component** |
| SEO + interactive (form) | Server shell + Client island |
| 매 entire page interactive (dashboard) | Mostly client, server outer layout |
**기본값**: 매 default Server Component — 매 boundary 를 leaf 에 push, 매 'use client' 매 minimum.
## 🔗 Graph
- 부모: [[React Server Components]]
- 변형: [[Modern_Web_Rendering_and_Optimization|Server Components]] · [[Server Actions]]
- 응용: [[Hydration]] · [[Suspense]] · [[Streaming SSR]]
- Adjacent: [[Islands Architecture]] · [[Astro]]
## 🤖 LLM 활용
**언제**: interactivity 매 필요한 leaf component, browser-only API, 매 form / input control.
**언제 X**: data fetching 매 only, static content — Server Component 가 더 light.
## ❌ 안티패턴
- **Root layout 매 'use client'**: 매 entire app 매 client bundle — 매 RSC benefit 의 destruction.
- **Server-only data 매 props 로 큰 객체 pass**: 매 RSC payload bloat.
- **Client component 안 server-only import** (e.g., `fs`, `db`): 매 build error / leak risk.
- **Server Component 안 useState**: 매 build error — 매 boundary 의 misunderstanding.
## 🧪 검증 / 중복
- Verified (React docs RSC 2026, Next.js 15 App Router guide).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — 'use client' boundary + composition rules + Server Action |
@@ -0,0 +1,167 @@
---
id: wiki-2026-0508-client-side-rendering-csr
title: Client-Side Rendering (CSR)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [CSR, SPA Rendering]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [rendering, csr, spa, frontend, performance]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React / Vue / Svelte
---
# Client-Side Rendering (CSR)
## 매 한 줄
> **"매 browser 가 매 HTML 을 그린다"**. CSR 은 server 가 빈 shell + JS bundle 만 보내고, browser 가 fetch + render 모두 수행 — 매 SPA 의 default mode, interactive app 에 강하나 매 first paint / SEO 매 weak.
## 매 핵심
### 매 lifecycle
1. Browser → server: `GET /` → minimal HTML + `<script src="bundle.js">`.
2. Browser parses HTML → fetches JS bundle.
3. JS executes → mounts framework → fetches data → renders DOM.
4. User interacts.
### 매 trade-off
| Pros | Cons |
|---|---|
| Rich interactivity | Slow TTI (특히 mobile) |
| Server cost low | SEO 매 needs hydration tricks |
| Client routing fast | Blank screen until JS loads |
| Offline-capable (PWA) | Bundle size matters a lot |
### 매 CSR vs SSR vs RSC (2026)
- CSR: dashboard, internal tool, app-like UX.
- SSR: marketing, blog, e-commerce.
- RSC: hybrid — server-render with client islands.
- SSG: docs, blog (rebuild on content change).
## 💻 패턴
### Vite + React CSR baseline
```tsx
// main.tsx
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<App />);
```
```html
<!-- index.html -->
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
```
### Route-based code splitting
```tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
export default function App() {
return (
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
```
### Skeleton-first paint (perceived perf)
```tsx
function UsersList() {
const { data, isLoading } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
if (isLoading) return <UsersSkeleton rows={10} />;
return <ul>{data!.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
```
### Pre-fetch on hover (link prefetch)
```tsx
<Link
to="/dashboard"
onMouseEnter={() => queryClient.prefetchQuery({ queryKey: ['dashboardData'], queryFn })}
>
Dashboard
</Link>
```
### Service Worker for offline shell
```ts
// sw.ts
self.addEventListener('install', (e: any) => {
e.waitUntil(caches.open('shell-v1').then((c) => c.addAll(['/', '/main.js', '/main.css'])));
});
self.addEventListener('fetch', (e: any) => {
e.respondWith(caches.match(e.request).then((r) => r ?? fetch(e.request)));
});
```
### Bundle budget enforcement
```js
// vite.config.ts
export default {
build: {
rollupOptions: {
output: {
manualChunks: { vendor: ['react', 'react-dom'] },
},
},
chunkSizeWarningLimit: 200, // KB
},
};
```
### SEO via prerender (when CSR + needed)
```bash
# Use prerender for marketing routes only
npx prerender-spa-plugin --routes /,/about,/pricing
```
## 매 결정 기준
| 상황 | Render mode |
|---|---|
| Auth-walled dashboard | CSR |
| Marketing site | SSG or SSR |
| Mixed app (e-commerce) | RSC / SSR + islands |
| Rich realtime (Figma-like) | CSR + WebSocket |
**기본값**: 매 user-app (login wall 뒤) → CSR. 매 public content → SSR/SSG/RSC.
## 🔗 Graph
- 부모: [[Rendering-Strategies]] · [[프론트엔드_및_UIUX_표준|Frontend-Architecture]]
- 변형: [[React-Server-Components]]
- Adjacent: [[Core Web Vitals Optimization (INP, LCP 개선)|Core-Web-Vitals]] · [[Code-Splitting]] · [[Hydration]]
## 🤖 LLM 활용
**언제**: app-like UX, auth-protected, heavy client interactivity.
**언제 X**: 매 SEO-critical public page, low-end device 가 주 audience.
## ❌ 안티패턴
- **Mega-bundle**: 매 single chunk 5MB → split routes / vendor.
- **No skeleton / loading state**: 매 blank screen 매 perceived as broken.
- **CSR for blog/docs**: 매 SEO/perf 매 모두 lose — SSG choice.
## 🧪 검증 / 중복
- Verified (web.dev / React docs / Vite docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — CSR fundamentals + tradeoffs + patterns |
@@ -0,0 +1,173 @@
---
id: wiki-2026-0508-component-composition
title: Component Composition (React)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [React Composition, Compound Components]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, composition, component-design, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React 19
---
# Component Composition (React)
## 매 한 줄
> **"매 inheritance 의 X, composition 의 O"**. React 의 design principle — UI 를 작은 composable component 로 분해, props.children + slot pattern + compound API 로 매 flexible 한 reuse 달성.
## 매 핵심
### 매 핵심 idioms
- `props.children` (default slot).
- Named slots (props as render fn / ReactNode).
- Compound components (parent + children share context).
- Render props / function-as-children.
- Polymorphic `as` prop.
- `React.cloneElement` / `Slot` (Radix).
### 매 vs Inheritance
- React 매 class extension 의 X — 매 wrapper component.
-`<Parent>` + `<Parent.Item>` style 매 implicit relation expression.
### 매 응용
1. Modal / Dialog (Header / Body / Footer slots).
2. Form fields (Field / Label / Input / Error).
3. Menu / Combobox (Radix-style headless).
4. Layout primitives (Stack, Grid).
## 💻 패턴
### Children as default slot
```tsx
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>;
}
// <Card><h2>Hi</h2><p>Body</p></Card>
```
### Named slots via props
```tsx
type Props = {
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
};
function Page({ header, footer, children }: Props) {
return (
<>
{header && <header>{header}</header>}
<main>{children}</main>
{footer && <footer>{footer}</footer>}
</>
);
}
```
### Compound components with Context
```tsx
import { createContext, useContext, useState } from 'react';
const TabsCtx = createContext<{ active: string; set: (v: string) => void } | null>(null);
function Tabs({ defaultValue, children }: { defaultValue: string; children: React.ReactNode }) {
const [active, set] = useState(defaultValue);
return <TabsCtx value={{ active, set }}>{children}</TabsCtx>;
}
function Tab({ value, children }: { value: string; children: React.ReactNode }) {
const { active, set } = useContext(TabsCtx)!;
return <button data-active={active === value} onClick={() => set(value)}>{children}</button>;
}
function Panel({ value, children }: { value: string; children: React.ReactNode }) {
const { active } = useContext(TabsCtx)!;
return active === value ? <div>{children}</div> : null;
}
Tabs.Tab = Tab;
Tabs.Panel = Panel;
export { Tabs };
// Usage:
// <Tabs defaultValue="a"><Tabs.Tab value="a">A</Tabs.Tab><Tabs.Panel value="a">…</Tabs.Panel></Tabs>
```
### Render prop
```tsx
function Toggle({ children }: { children: (state: { on: boolean; toggle: () => void }) => React.ReactNode }) {
const [on, setOn] = useState(false);
return <>{children({ on, toggle: () => setOn((v) => !v) })}</>;
}
// <Toggle>{({ on, toggle }) => <button onClick={toggle}>{on ? 'on' : 'off'}</button>}</Toggle>
```
### Polymorphic `as` prop
```tsx
type AsProp<T extends React.ElementType> = { as?: T } & React.ComponentPropsWithoutRef<T>;
function Box<T extends React.ElementType = 'div'>({ as, ...rest }: AsProp<T>) {
const Tag = as ?? 'div';
return <Tag {...rest} />;
}
// <Box as="section" className="x">…</Box>
```
### Radix Slot pattern (asChild)
```tsx
import { Slot } from '@radix-ui/react-slot';
function Button({ asChild, ...props }: { asChild?: boolean } & React.ComponentProps<'button'>) {
const Comp = asChild ? Slot : 'button';
return <Comp className="btn" {...props} />;
}
// <Button asChild><a href="/x">link styled as button</a></Button>
```
### Layout primitives (Stack)
```tsx
function Stack({ gap = 8, children }: { gap?: number; children: React.ReactNode }) {
return <div style={{ display: 'flex', flexDirection: 'column', gap }}>{children}</div>;
}
```
## 매 결정 기준
| 상황 | Pattern |
|---|---|
| Single content area | `children` |
| Multiple structured slots | Named props or compound |
| Tightly-coupled siblings | Compound + context |
| Behavior + UI separation | Render props / hook |
| Style polymorphism | `as` prop or `Slot` |
**기본값**: 매 children prop 으로 시작 → 복잡해지면 compound + context.
## 🔗 Graph
- 부모: [[React]]
- 변형: [[Web-Components]]
- 응용: [[Radix-UI]] · [[Headless-UI]] · [[shadcn]]
- Adjacent: [[Server-Components]] · [[Composition API]]
## 🤖 LLM 활용
**언제**: design system 구축, library API 설계, complex form/dialog UI.
**언제 X**: 매 trivial leaf component — over-engineering.
## ❌ 안티패턴
- **Prop explosion**: 매 20+ props → 매 compound 로 분해.
- **`cloneElement` everywhere**: 매 fragile, prefer context.
- **Deep prop drilling**: 매 context or compound 로 해결.
## 🧪 검증 / 중복
- Verified (react.dev / Radix UI patterns / Kent Dodds blog).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — React composition idioms + compound pattern |
@@ -0,0 +1,186 @@
---
id: wiki-2026-0508-composition-api
title: Composition API (Vue 3)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Vue Composition API, setup script]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [vue, composition-api, reactivity, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Vue 3
---
# Composition API (Vue 3)
## 매 한 줄
> **"매 reactive primitive 으로 logic 을 조합한다"**. Composition API 는 Vue 3 의 `setup()` / `<script setup>` 기반 model — `ref`, `reactive`, `computed`, `watch` 를 직접 import 하여 매 logic 조각을 자유 조합, 매 Options API 의 `data/methods/computed` partition 을 대체.
## 매 핵심
### 매 핵심 primitives
- `ref(v)`: 매 wraps any value, `.value` access.
- `reactive(obj)`: deep proxy — object/array 만.
- `computed(fn)`: derived ref, lazy + cached.
- `watch(src, cb)`: explicit deps + cb.
- `watchEffect(fn)`: auto-track, eager.
- `effectScope()`: manual lifecycle group.
### 매 vs Options API
| Aspect | Options | Composition |
|---|---|---|
| Logic reuse | mixins (collision-prone) | composables (clean) |
| TypeScript | OK | excellent |
| File length scaling | grows by category | grows by feature |
| Learning curve | gentle | steeper but worth it |
### 매 `<script setup>` perks
- Top-level await.
- Auto-expose for template.
- `defineProps`, `defineEmits`, `defineModel`, `defineExpose` macros.
- Compile-time optimizations (no `setup()` boilerplate).
## 💻 패턴
### Basic setup with ref + computed
```vue
<script setup lang="ts">
import { ref, computed } from 'vue';
const count = ref(0);
const double = computed(() => count.value * 2);
const inc = () => count.value++;
</script>
<template>
<button @click="inc">{{ count }} (×2 = {{ double }})</button>
</template>
```
### Typed props + emits + v-model
```vue
<script setup lang="ts">
const props = defineProps<{ initial?: number }>();
const emit = defineEmits<{ change: [value: number] }>();
const model = defineModel<string>({ default: '' });
</script>
<template>
<input v-model="model" />
</template>
```
### reactive with toRefs (avoid losing reactivity)
```ts
import { reactive, toRefs } from 'vue';
export function useUser() {
const state = reactive({ name: 'Ada', age: 36 });
return { ...toRefs(state) }; // 매 destructurable + reactive
}
```
### watch with explicit source
```ts
import { ref, watch } from 'vue';
const query = ref('');
watch(query, async (q, _old, onCleanup) => {
const ctrl = new AbortController();
onCleanup(() => ctrl.abort());
const r = await fetch(`/search?q=${q}`, { signal: ctrl.signal });
// ...
});
```
### watchEffect (auto-track)
```ts
import { ref, watchEffect } from 'vue';
const userId = ref(1);
watchEffect(async () => {
const r = await fetch(`/api/users/${userId.value}`);
user.value = await r.json();
});
```
### Provide / inject (typed)
```ts
// keys.ts
import type { InjectionKey, Ref } from 'vue';
export const ThemeKey: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme');
// Parent
import { provide, ref } from 'vue';
const theme = ref<'light' | 'dark'>('dark');
provide(ThemeKey, theme);
// Child
import { inject } from 'vue';
const theme = inject(ThemeKey)!;
```
### Async setup with Suspense
```vue
<!-- Parent -->
<Suspense>
<UserProfile :id="42" />
<template #fallback><Skeleton /></template>
</Suspense>
<!-- UserProfile.vue -->
<script setup lang="ts">
const props = defineProps<{ id: number }>();
const user = await (await fetch(`/users/${props.id}`)).json();
</script>
```
### Lifecycle hooks
```ts
import { onMounted, onUnmounted } from 'vue';
onMounted(() => console.log('mounted'));
onUnmounted(() => console.log('cleanup'));
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| New Vue 3 project | Composition + `<script setup>` |
| Migrating from Vue 2 | Options 유지 → 점진 conversion |
| Logic reuse needed | Composable function |
| Simple 1-off component | Either OK, prefer setup for TS |
**기본값**: `<script setup>` + Composition API. Options API 는 legacy maintenance only.
## 🔗 Graph
- 부모: [[Vue 3]]
- 변형: [[Options-API]] · [[Solid-Signals]]
- 응용: [[Composables]] · [[Pinia]] · [[Nuxt]]
- Adjacent: [[Component-Composition]] · [[TypeScript]]
## 🤖 LLM 활용
**언제**: Vue 3 component 작성, composable 추출, TS 강한 typing 필요.
**언제 X**: Vue 2.7 이전 — 매 backport limited.
## ❌ 안티패턴
- **Mixing reactive() destructure without toRefs**: loses reactivity silently.
- **Using `ref.value` in template**: 매 unwrap 자동, `.value` 의 X.
- **Excessive `watch`**: 매 computed 로 충분한 경우 매 prefer computed.
## 🧪 검증 / 중복
- Verified (vuejs.org official guide).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Composition API primitives + setup patterns |
@@ -0,0 +1,193 @@
---
id: wiki-2026-0508-concurrent-features
title: Concurrent Features (React 18+)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [React Concurrent Mode, useTransition, useDeferredValue]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, concurrent, performance, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React 19
---
# Concurrent Features (React 18+)
## 매 한 줄
> **"매 render 매 interruptible 하다"**. React 18 부터 매 concurrent renderer — `useTransition`, `useDeferredValue`, `Suspense`, automatic batching, streaming SSR 매 모두 매 high-priority update 가 low-priority 를 preempt 가능한 architecture 위에 build.
## 매 핵심
### 매 핵심 features
- **Automatic batching**: promise / setTimeout / native event 모두 batch.
- **`startTransition` / `useTransition`**: 매 non-urgent state update marking.
- **`useDeferredValue`**: input 매 lag 없이 expensive list 매 deferred.
- **`Suspense`**: data fetching boundary, fallback UI.
- **Streaming SSR**: `renderToPipeableStream` (Node), `renderToReadableStream` (Edge).
- **`useId`**: SSR-safe unique id.
- **`use()` hook (React 19)**: unwrap promise/context inside render.
### 매 priority levels
1. Sync / urgent (user input).
2. Default (most state updates).
3. Transition (`startTransition`).
4. Idle.
### 매 응용
1. 매 typeahead search — input snappy + results deferred.
2. Tab switching — 매 stale UI keeps interactive.
3. Streaming page render — 매 fastest paint then progressively fill.
## 💻 패턴
### useTransition for non-urgent updates
```tsx
import { useState, useTransition } from 'react';
function Filter({ items }: { items: Item[] }) {
const [query, setQuery] = useState('');
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
const onChange = (v: string) => {
setQuery(v); // urgent — input responds immediately
startTransition(() => {
setFiltered(items.filter((i) => i.name.includes(v))); // non-urgent
});
};
return (
<>
<input value={query} onChange={(e) => onChange(e.target.value)} />
{isPending && <Spinner />}
<List items={filtered} />
</>
);
}
```
### useDeferredValue
```tsx
import { useState, useDeferredValue, memo } from 'react';
function Search({ items }: { items: Item[] }) {
const [query, setQuery] = useState('');
const deferred = useDeferredValue(query);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<SlowList items={items} query={deferred} />
</>
);
}
const SlowList = memo(({ items, query }: { items: Item[]; query: string }) => {
return <ul>{items.filter((i) => i.name.includes(query)).map((i) => <li key={i.id}>{i.name}</li>)}</ul>;
});
```
### Suspense boundary
```tsx
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<Skeleton />}>
<UserProfile id={42} />
<Suspense fallback={<PostsSkeleton />}>
<Posts userId={42} />
</Suspense>
</Suspense>
);
}
```
### use() hook (React 19)
```tsx
import { use, Suspense } from 'react';
function Profile({ promise }: { promise: Promise<User> }) {
const user = use(promise); // 매 unwrap inside render
return <h1>{user.name}</h1>;
}
// caller
<Suspense fallback={<Spinner />}>
<Profile promise={fetchUser(42)} />
</Suspense>
```
### Streaming SSR (Node)
```ts
import { renderToPipeableStream } from 'react-dom/server';
server.get('/', (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() { res.statusCode = 200; pipe(res); },
onError(err) { console.error(err); },
});
});
```
### Automatic batching across async
```tsx
function handleClick() {
// React 18+: 매 둘 다 single render
setTimeout(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
}, 0);
}
```
### flushSync for opt-out
```tsx
import { flushSync } from 'react-dom';
flushSync(() => setItems(next)); // 매 sync flush — DOM ready
scrollToBottom(); // 매 새 item 매 already mounted
```
## 매 결정 기준
| 상황 | Feature |
|---|---|
| Slow filter blocks input | `useTransition` or `useDeferredValue` |
| Async data in component tree | `Suspense` + `use()` |
| Need DOM after update | `flushSync` |
| SSR slow first byte | streaming SSR |
| Pre-React-18 codebase | upgrade first |
**기본값**: input UI 매 lag → `useDeferredValue`. Bigger state cascade → `useTransition`.
## 🔗 Graph
- 부모: [[React]] · [[Performance]]
- 변형: [[Suspense]] · [[Server-Components]] · [[Streaming-SSR]]
- 응용: [[Remix]]
- Adjacent: [[Code-Splitting]]
## 🤖 LLM 활용
**언제**: 매 input lag, expensive list, async-heavy tree, SSR perf.
**언제 X**: 매 trivial UI, 매 add-only sync state.
## ❌ 안티패턴
- **`startTransition` for urgent input**: 매 input 매 still feel laggy.
- **No `Suspense` boundary 위 `use()`**: 매 throws 매 above-tree fallback 까지 propagate.
- **Mixing `flushSync` everywhere**: 매 defeats concurrent benefits.
## 🧪 검증 / 중복
- Verified (react.dev official docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — React 18/19 concurrent features + patterns |
@@ -0,0 +1,154 @@
---
id: wiki-2026-0508-diffing-algorithm
title: Diffing Algorithm
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [VDOM Diff, Reconciliation]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, vue, vdom, reconciliation]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript
framework: React/Vue
---
# Diffing Algorithm
## 매 한 줄
> **"매 VDOM diff = O(n) heuristic — 매 same-level node 의 비교 + key-based identity."**. 매 React/Vue/Preact 모두 매 Levenshtein-style O(n³) 의 회피하기 위해 매 두 가정 (1) 다른 type = subtree 교체, (2) key가 child identity 의 hint. 매 React 19 (2024) Fiber + concurrent rendering, Vue 3.4 patchFlag-driven static hoist 의 진화.
## 매 핵심
### 매 두 가정 (Heuristics)
1. 매 different element type ⇒ subtree 의 unmount + 재생성.
2. 매 stable `key` ⇒ list reconciliation 의 identity hint.
### 매 알고리즘
- React = Fiber tree, work loop가 매 cooperative scheduling 가능 (concurrent mode).
- Vue 3 = compiled patchFlag (static / dynamic 분류) + LIS (Longest Increasing Subsequence) for keyed list.
- Svelte/Solid = 매 VDOM 없음 — 매 fine-grained reactivity 의 directly DOM 의 patch.
### 매 응용
1. Keyed list — 매 stable key가 매 reorder cost 의 minimize.
2. Conditional render — 매 ternary가 매 type swap 의 발생.
3. Concurrent rendering — 매 high-priority update가 매 low-priority interrupt.
## 💻 패턴
### 1. Stable key (correct)
```jsx
// CORRECT — id is stable identity
items.map((item) => <Row key={item.id} item={item} />)
// WRONG — index changes when list reorders
items.map((item, i) => <Row key={i} item={item} />)
```
### 2. Type swap forces unmount
```jsx
{condition ? <div>A</div> : <span>A</span>}
// span/div type 다름 → subtree unmount + remount
// 같은 component reuse 원하면 같은 type 유지:
<div>{condition ? 'A' : 'B'}</div>
```
### 3. React Fiber priority lanes (19)
```jsx
import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();
const onChange = (e) => {
setInput(e.target.value); // urgent
startTransition(() => {
setFilter(e.target.value); // can be interrupted
});
};
```
### 4. Vue 3 patchFlag (compiled)
```html
<!-- Source -->
<div>
<span>{{ msg }}</span>
<span class="static">hi</span>
</div>
<!-- Compiled (simplified) -->
<!-- 첫 span has patchFlag = 1 (TEXT) → diff only text -->
<!-- 두번째 span hoisted as static — 매 diff 건너뛰기 -->
```
### 5. Vue keyed list w/ LIS
```html
<TransitionGroup tag="ul">
<li v-for="item in items" :key="item.id">{{ item.label }}</li>
</TransitionGroup>
<!-- Vue calculates LIS to minimize DOM moves -->
```
### 6. React.memo + structural equality
```typescript
const Row = React.memo(({ item }: { item: Item }) =>
<div>{item.label}</div>,
(prev, next) => prev.item.id === next.item.id && prev.item.label === next.item.label,
);
```
### 7. Solid/Svelte alternative (no diff)
```typescript
// Solid — fine-grained reactivity, no VDOM
import { createSignal } from 'solid-js';
const [count, setCount] = createSignal(0);
// JSX compiles to direct DOM ops; only `count()` text node updates
return <div>count: {count()}</div>;
```
### 8. Avoid layout-impacting reorder
```jsx
// Stable order with key — only reorder DOM, no remount
<>{sorted.map((u) => <UserCard key={u.id} user={u} />)}</>
// Each UserCard preserves its instance (state, refs) on reorder.
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| List render | 매 stable id key — index X. |
| Heavy filter + input | useTransition + deferredValue. |
| Static UI | Vue patchFlag / React.memo. |
| Maximum perf | Solid / Svelte (skip VDOM). |
| Type-switch UI | wrap in same outer type to preserve children. |
**기본값**: 매 React 19 + Suspense + Transition + 매 stable key.
## 🔗 Graph
- 부모: [[Virtual_DOM과_Reconciliation|Virtual DOM]] · [[Reconciliation]]
- 변형: [[React Fiber]] · [[Vue Reactivity]]
- 응용: [[Animation]]
## 🤖 LLM 활용
**언제**: 매 keyed-list bug 의 explain, 매 type-swap unmount 의 진단.
**언제 X**: 매 specific framework internal — 매 source code 의 read.
## ❌ 안티패턴
- **Index as key with reorder**: 매 wrong identity → state mix.
- **Random key per render**: 매 매 unmount + remount.
- **Inline objects/arrays as deps**: 매 referential change → memo bypass.
- **Massive un-keyed list**: 매 O(n) DOM ops 매 frame.
## 🧪 검증 / 중복
- Verified (react.dev reconciliation, vuejs.org renderer, Solid docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — VDOM diff + Fiber + Vue patchFlag |
@@ -0,0 +1,206 @@
---
id: wiki-2026-0508-draw-call-optimization
title: Draw Call Optimization
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Batching, Instancing, GPU Draw Reduction]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [graphics, gpu, performance, webgl, webgpu]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: WebGPU
---
# Draw Call Optimization
## 매 한 줄
> **"매 CPU→GPU command submission 의 의 minimize 의 — 의 frame budget 의 dominant cost"**. 의 each draw call 의 의 driver overhead (state validation, command translation) 의 incur. 2026 의 WebGPU 의 매 explicit 의 design 의 의 batching 의 essential 의.
## 매 핵심
### 매 cost 의 source
- 의 driver state validation (shader, buffer, texture binding).
- 의 command buffer 의 translation 의 GPU-specific ISA.
- 의 GPU 의 의 pipeline switch (cache miss, warp reorganize).
### 매 reduction 의 strategy
- **Batching**: 의 same-state object 의 single draw 의 의 merge.
- **Instancing**: 의 same mesh 의 N copy 의 single 의 draw call 의 issue.
- **Texture atlas**: 의 multiple texture 의 의 single 의 의 — 의 binding 의 reduce.
- **Indirect draw**: 의 GPU 의 의 self-issue 의 의 — CPU 의 의 idle.
- **Bindless / large bind group**: 의 binding 의 의 amortize.
### 매 응용
1. UI rendering (의 button 의 thousand 의 single draw).
2. Particle system (의 instancing 의 의 millions).
3. Tilemap (atlas + instancing).
4. Foliage / crowd (의 GPU instancing).
5. Game world chunk (의 batching 의 의 static mesh).
## 💻 패턴
### Three.js BatchedMesh
```ts
import * as THREE from "three";
const batched = new THREE.BatchedMesh(1024, 60_000, 90_000, material);
const cubeGeom = new THREE.BoxGeometry();
const id = batched.addGeometry(cubeGeom);
for (let i = 0; i < 1024; i++) {
const inst = batched.addInstance(id);
const m = new THREE.Matrix4().setPosition(Math.random() * 100, 0, Math.random() * 100);
batched.setMatrixAt(inst, m);
}
scene.add(batched);
// 매 single draw call 의 1024 cube
```
### InstancedMesh
```ts
const geom = new THREE.SphereGeometry(0.5);
const mesh = new THREE.InstancedMesh(geom, material, 10_000);
const m = new THREE.Matrix4();
for (let i = 0; i < 10_000; i++) {
m.setPosition(Math.random() * 200 - 100, 0, Math.random() * 200 - 100);
mesh.setMatrixAt(i, m);
}
mesh.instanceMatrix.needsUpdate = true;
```
### WebGPU instanced draw
```ts
const pass = encoder.beginRenderPass(passDesc);
pass.setPipeline(pipeline);
pass.setBindGroup(0, sceneBindGroup);
pass.setVertexBuffer(0, vertexBuffer);
pass.setVertexBuffer(1, instanceBuffer); // per-instance data
pass.setIndexBuffer(indexBuffer, "uint32");
pass.drawIndexed(indexCount, instanceCount);
pass.end();
```
### WebGPU indirect draw (GPU 의 self-issue)
```ts
const indirectBuffer = device.createBuffer({
size: 16, // [vertexCount, instanceCount, firstVertex, firstInstance]
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE,
});
// 의 compute shader 의 의 indirectBuffer 의 의 fill (e.g. frustum cull)
pass.drawIndirect(indirectBuffer, 0);
```
### Texture atlas (UV 의 sub-region)
```glsl
// fragment shader
uniform sampler2D atlas;
uniform vec4 uvRect; // x, y, w, h
void main() {
vec2 uv = uvRect.xy + v_uv * uvRect.zw;
outColor = texture(atlas, uv);
}
```
### Sort 의 의 state-change minimize
```ts
// 의 draw 의 의 material 의 의 sort
drawables.sort((a, b) => {
if (a.materialId !== b.materialId) return a.materialId - b.materialId;
if (a.meshId !== b.meshId) return a.meshId - b.meshId;
return a.depth - b.depth;
});
```
### UI batching (single quad mesh)
```ts
// 의 each UI element 의 의 quad 의 의 single VBO 의 의 append
class UIBatcher {
vertices = new Float32Array(4096 * 4 * 5); // x, y, u, v, color
count = 0;
pushQuad(x: number, y: number, w: number, h: number, uv: UVRect, color: number) {
const v = this.vertices;
const o = this.count * 20;
v[o+0]=x; v[o+1]=y; v[o+2]=uv.x; v[o+3]=uv.y; v[o+4]=color;
v[o+5]=x+w; v[o+6]=y; v[o+7]=uv.x+uv.w; v[o+8]=uv.y; v[o+9]=color;
v[o+10]=x+w; v[o+11]=y+h; v[o+12]=uv.x+uv.w; v[o+13]=uv.y+uv.h; v[o+14]=color;
v[o+15]=x; v[o+16]=y+h; v[o+17]=uv.x; v[o+18]=uv.y+uv.h; v[o+19]=color;
this.count++;
}
flush(pass: GPURenderPassEncoder) {
device.queue.writeBuffer(this.vbo, 0, this.vertices, 0, this.count * 20);
pass.setVertexBuffer(0, this.vbo);
pass.draw(6 * this.count); // 매 single call
this.count = 0;
}
}
```
### Frustum cull (CPU)
```ts
function cull(objects: Drawable[], camera: Camera): Drawable[] {
const frustum = camera.frustum;
return objects.filter((o) => frustum.intersects(o.worldBounds));
}
```
### GPU-driven cull (compute)
```wgsl
@compute @workgroup_size(64)
fn cullCS(@builtin(global_invocation_id) gid: vec3u) {
let i = gid.x;
if (i >= arrayLength(&instances)) { return; }
let inst = instances[i];
if (frustumIntersects(inst.bounds, frustum)) {
let slot = atomicAdd(&drawCount, 1u);
visibleInstances[slot] = inst;
}
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Same mesh, many copy | InstancedMesh (instancing) |
| Different mesh, same material | BatchedMesh (geometry merge) |
| UI / 2D | Sprite batcher + atlas |
| Static scene | Pre-merge geometry at build time |
| Dynamic LOD / cull | GPU indirect draw + compute cull |
| Mobile / tile | Reduce binding, atlas, instancing |
**기본값**: instancing 의 first, batching 의 second, indirect/compute 의 last.
## 🔗 Graph
- 부모: [[GPU Pipeline]] · [[Real-Time Rendering]]
- 변형: [[Instancing]] · [[Batching]] · [[Indirect Draw]]
- Adjacent: [[Texture Atlas]] · [[GPU Driven Rendering]] · [[Frustum Culling]]
## 🤖 LLM 활용
**언제**: 의 frame time 의 의 CPU-bound 의 (draw call > 1000), GPU-driven culling, atlas 설계.
**언제 X**: 의 매 GPU-bound 의 (fragment-heavy) — 의 다른 의 axis 의 (overdraw, shader complexity) 의 attack.
## ❌ 안티패턴
- **One mesh per object**: 의 10,000 entity 의 = 의 10,000 draw — 매 disaster.
- **Per-frame buffer recreate**: 의 GC pressure + 의 driver overhead.
- **Random material switch**: state thrash — 매 sort 의 의 by material first.
- **Premature GPU-driven**: 의 CPU 의 매 not bottleneck 의 시 의 — 매 added complexity.
## 🧪 검증 / 중복
- Verified (WebGPU spec, Three.js BatchedMesh r167+, Unreal/Unity rendering docs, GPU Gems, RenderDoc analysis).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — instancing + indirect + UI batcher 추가 |
@@ -0,0 +1,214 @@
---
id: wiki-2026-0508-error-boundaries
title: Error Boundaries
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [React Error Boundary, ErrorBoundary, getDerivedStateFromError]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, error-handling, frontend, resilience]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React
---
# Error Boundaries
## 매 한 줄
> **"매 React subtree 의 의 render-time error 의 catch 의 의 fallback UI 의 의 swap 의 component"**. 의 entire app crash 의 의 prevent 의 — 의 try/catch 의 declarative React equivalent. 2026 의 의 매 React 19 의 still 의 class component 의만 의 의 Error Boundary 의 author 의 가능 (의 hook API 의 X).
## 매 핵심
### 매 catch 의 X 의 것
- Event handler error (의 try/catch 의 사용).
- Async code (setTimeout, promise) — Error Boundary 의 의 reach 의 X.
- SSR (의 server-side throw 의 의 catch 의 의 X).
- Boundary itself 의 throw.
### 매 catch 의 것
- 의 child render error.
- 의 lifecycle method error.
- 의 constructor error.
### 매 hierarchy strategy
- **Top-level**: 의 fallback "Something went wrong" 의 의 entire app crash 의 의 prevent.
- **Route-level**: 의 broken page 의만 의 의 fail.
- **Widget-level**: 의 chart, embed, third-party 의 의 isolate.
### 매 응용
1. Production crash 의 reporting (Sentry, Datadog).
2. Third-party widget 의 isolation.
3. Per-route error UI (Next.js `error.tsx`).
4. Suspense fallback combo (loading + error).
## 💻 패턴
### 기본 class boundary
```tsx
import { Component, type ReactNode, type ErrorInfo } from "react";
type Props = { fallback: ReactNode; onError?: (e: Error, info: ErrorInfo) => void; children: ReactNode };
type State = { hasError: boolean };
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
this.props.onError?.(error, info);
}
render() {
return this.state.hasError ? this.props.fallback : this.props.children;
}
}
```
### react-error-boundary 라이브러리
```tsx
import { ErrorBoundary } from "react-error-boundary";
function Fallback({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Retry</button>
</div>
);
}
<ErrorBoundary
FallbackComponent={Fallback}
onError={(error, info) => Sentry.captureException(error, { extra: info })}
onReset={() => queryClient.resetQueries()}
>
<Dashboard />
</ErrorBoundary>
```
### Suspense + ErrorBoundary 의 combo
```tsx
<ErrorBoundary FallbackComponent={Fallback}>
<Suspense fallback={<Skeleton />}>
<UserProfile id={userId} />
</Suspense>
</ErrorBoundary>
```
### 의 async event 의 의 manual rethrow
```tsx
function useAsyncError() {
const [, setState] = useState();
return useCallback((e: unknown) => {
setState(() => { throw e; });
}, []);
}
function MyComponent() {
const throwAsync = useAsyncError();
const onClick = async () => {
try {
await fetchData();
} catch (e) {
throwAsync(e); // 매 ErrorBoundary 의 의 reach
}
};
return <button onClick={onClick}>Load</button>;
}
```
### Next.js App Router `error.tsx`
```tsx
"use client";
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => { console.error(error); }, [error]);
return (
<div>
<h2>Something went wrong</h2>
<button onClick={reset}>Retry</button>
</div>
);
}
```
### Per-route boundary
```tsx
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={
<ErrorBoundary FallbackComponent={HomeFailed}><Home /></ErrorBoundary>
} />
<Route path="dashboard" element={
<ErrorBoundary FallbackComponent={DashFailed}><Dashboard /></ErrorBoundary>
} />
</Route>
</Routes>
```
### 의 Sentry 의 integrate
```tsx
import * as Sentry from "@sentry/react";
const SentryBoundary = Sentry.withErrorBoundary(MyApp, {
fallback: ({ error, resetError }) => <Fallback error={error} resetErrorBoundary={resetError} />,
showDialog: true,
});
```
### Reset on prop change
```tsx
<ErrorBoundary
FallbackComponent={Fallback}
resetKeys={[userId]} // 의 userId 의 의 change 의 시 의 boundary reset
>
<UserProfile id={userId} />
</ErrorBoundary>
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Top-level app crash 의 prevent | Single root boundary + telemetry |
| Route 의 isolation | Per-route boundary (Next.js `error.tsx`) |
| Third-party widget | Tight boundary 의 의 widget 의만 |
| Async data fetch | TanStack Query 의 `throwOnError` + boundary |
| Form validation | Inline error UI — 매 boundary 의 X |
**기본값**: root + per-route + per-widget — 매 layered boundaries.
## 🔗 Graph
- 부모: [[React]] · [[Error Handling]]
- 변형: [[Suspense]] · [[react-error-boundary]]
- Adjacent: [[Discriminated Unions for Error Handling]]
## 🤖 LLM 활용
**언제**: production crash protection, third-party widget isolation, Suspense + error 의 combo.
**언제 X**: form-level validation — 매 inline UI 의 더 적합.
## ❌ 안티패턴
- **No top-level boundary**: 의 single child throw 의 의 entire app white-screen.
- **Boundary over async without rethrow**: 의 catch 의 의 fail — 의 manual rethrow 의 필요.
- **Eat error silently**: 매 telemetry 의 의 X — debug 의 impossible.
- **Reset 의 의 X**: user 의 의 retry 의 의 way 의 X — stuck 의 fallback.
- **getDerivedStateFromError 의 의 side effect**: pure function 의 의 의 violation.
## 🧪 검증 / 중복
- Verified (React 19 docs, react-error-boundary, Next.js App Router error handling, Sentry React SDK).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — async rethrow + Next.js error.tsx + reset key 추가 |
@@ -0,0 +1,189 @@
---
id: wiki-2026-0508-functional-programming-in-typesc
title: Functional Programming in TypeScript
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [FP TS, fp-ts, Effect-TS, Functional TypeScript]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, functional-programming, effect-ts, fp-ts]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: effect-ts
---
# Functional Programming in TypeScript
## 매 한 줄
> **"매 type system 이 매 effect 와 매 error 를 매 first-class 로 추적한다."**. Pure function + immutable + typed effect. 매 2026 의 FP-TS landscape 는 매 Effect-TS 가 매 dominant — fp-ts/io-ts 의 매 후계자.
## 매 핵심
### 매 원칙
- **Purity**: 매 같은 input → 매 같은 output. 매 side effect X.
- **Immutability**: 매 mutation X. 매 readonly + structural copy.
- **Composition**: 매 small function 의 매 pipe / flow.
- **Total functions**: 매 모든 input 에 매 정의된 output. 매 throw X — 매 Either / Effect.
- **Typed errors**: 매 error type 을 매 signature 에 매 표현.
### 매 핵심 구조
- **Option**: nullable 의 매 type-safe 표현.
- **Either**: success / failure union.
- **Effect**: 매 lazy computation + dependency + error type — `Effect<A, E, R>`.
- **Stream**: 매 lazy async iterator.
- **Schema**: 매 runtime + static type.
### 매 응용
1. API client — 매 typed error / retry / timeout 의 매 declarative.
2. Form validation — 매 Schema parse + Either.
3. Concurrent workflow — 매 Effect.all + structured concurrency.
## 💻 패턴
### Option / Either basics (Effect-TS)
```typescript
import { Option, Either } from 'effect';
const safeDiv = (a: number, b: number): Option.Option<number> =>
b === 0 ? Option.none() : Option.some(a / b);
const parsed: Either.Either<number, string> =
Either.try({ try: () => JSON.parse('{"x":1}').x, catch: e => String(e) });
```
### Pipe + flow
```typescript
import { pipe, flow } from 'effect';
const upper = (s: string) => s.toUpperCase();
const exclaim = (s: string) => `${s}!`;
const shout = flow(upper, exclaim);
console.log(pipe('hello', shout)); // "HELLO!"
```
### Effect with typed errors
```typescript
import { Effect } from 'effect';
class NotFoundError { readonly _tag = 'NotFoundError' }
class NetworkError { readonly _tag = 'NetworkError' }
const fetchUser = (id: string): Effect.Effect<User, NotFoundError | NetworkError> =>
Effect.tryPromise({
try: () => fetch(`/u/${id}`).then(r => {
if (r.status === 404) throw new NotFoundError();
return r.json();
}),
catch: e => e instanceof NotFoundError ? e : new NetworkError(),
});
```
### Service / dependency injection
```typescript
import { Context, Effect, Layer } from 'effect';
class Logger extends Context.Tag('Logger')<Logger, { log: (m: string) => void }>() {}
const program = Effect.gen(function* () {
const logger = yield* Logger;
logger.log('hi');
});
const LoggerLive = Layer.succeed(Logger, { log: console.log });
Effect.runSync(Effect.provide(program, LoggerLive));
```
### Schema validation (runtime + static)
```typescript
import { Schema } from 'effect';
const User = Schema.Struct({
id: Schema.String,
age: Schema.Number.pipe(Schema.between(0, 150)),
email: Schema.String.pipe(Schema.pattern(/^[^@]+@[^@]+$/)),
});
type User = Schema.Schema.Type<typeof User>;
const parseUser = Schema.decodeUnknownEither(User);
```
### Structured concurrency
```typescript
const fetchAll = Effect.all(
[fetchUser('a'), fetchUser('b'), fetchUser('c')],
{ concurrency: 2 } // limit
).pipe(
Effect.timeout('5 seconds'),
Effect.retry({ times: 3 }),
);
```
### Immutable update with Optic
```typescript
import { Lens } from 'monocle-ts';
interface Order { user: { name: string } }
const userName = Lens.fromPath<Order>()(['user', 'name']);
const upd = userName.modify(s => s.toUpperCase());
const next = upd({ user: { name: 'foo' } }); // structural copy
```
### Discriminated union exhaustiveness
```typescript
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; s: number };
const area = (sh: Shape): number => {
switch (sh.kind) {
case 'circle': return Math.PI * sh.r ** 2;
case 'square': return sh.s ** 2;
default: { const _: never = sh; return _; } // compile error if missed
}
};
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| 매 nullable | Option (or `T \| undefined` + helper) |
| 매 expected error | Either / Effect typed error |
| 매 unexpected (bug) | throw — 매 error boundary |
| 매 async + DI + retry | Effect-TS |
| 매 simple validation | Zod (lighter) |
| 매 large schema + transform | Effect Schema |
**기본값**: 매 plain TS + readonly + discriminated union. 매 복잡한 effect orchestration 은 매 Effect-TS.
## 🔗 Graph
- 부모: [[TypeScript]] · [[Functional Programming]]
- 변형: [[Effect-TS]] · [[fp-ts]]
- 응용: [[Validation]] · [[API Client]] · [[State Management]]
- Adjacent: [[Zod]]
## 🤖 LLM 활용
**언제**: 매 typed error model 설계, 매 schema-first API, 매 effect-heavy domain code.
**언제 X**: 매 simple CRUD UI — 매 overkill.
## ❌ 안티패턴
- **Effect 모든 곳**: 매 over-abstraction. 매 leaf function 은 매 plain.
- **`any` escape hatch**: 매 type 의 매 의미 손실.
- **Mutating in pipe**: 매 immutability 위반.
- **Throw inside Effect.try with rethrow**: 매 typed error 회피.
## 🧪 검증 / 중복
- Verified (Effect-TS 3.x docs, fp-ts legacy migration guide).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — 매 Effect-TS 기준 패턴 + Schema |
@@ -0,0 +1,34 @@
---
id: wiki-2026-0508-hydration-progressive-rendering
title: "Hydration & Progressive Rendering"
category: 10_Wiki/Topics
status: duplicate
canonical_id: hydration
duplicate_of: "[[Hydration]]"
aliases: []
source_trust_level: A
confidence_score: 0.9
verification_status: redirected
tags: [duplicate, frontend, ssr, hydration]
last_reinforced: 2026-05-10
github_commit: pending
---
# Hydration & Progressive Rendering
> **이 문서는 [[Hydration]] 의 중복본입니다.** Canonical 문서로 redirect.
## 핵심 요약
- **Hydration**: SSR HTML 에 client-side event handler / state 의 attach.
- **Progressive rendering**: 매 React 18 streaming SSR + Suspense — 매 chunk 단위 stream + selective hydration.
- **Resumability** (Qwik): hydration 자체 의 회피 — 매 serialized state 를 즉시 복원.
## 🔗 Graph
- 부모: [[Hydration]] (canonical)
- Adjacent: [[Streaming-SSR]] · [[React-Server-Components]]
## 🕓 변경 이력
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | 중복 처리 — canonical 문서로 redirect |
@@ -0,0 +1,200 @@
---
id: wiki-2026-0508-jsi-javascript-interface
title: JSI (JavaScript Interface)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [React Native JSI, JavaScript Interface, RN New Architecture]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react-native, jsi, performance, native-modules]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: cpp
framework: react-native
---
# JSI (JavaScript Interface)
## 매 한 줄
> **"매 bridge 의 JSON serialization 의 제거 — 매 sync, direct C++ binding"**. React Native 의 New Architecture 의 foundation. 매 native function 을 C++ 객체로 expose, JS 가 매 sync 호출 + shared memory 접근. 매 old bridge 의 async-only / serialize-everything 의 근본 해결.
## 매 핵심
### 매 old bridge 문제
- 모든 JS↔Native 호출 의 JSON serialize → message queue → async dispatch.
- 매 frame 마다 hundreds of message — main thread blocking.
- Image, animation 의 latency 누적.
### 매 JSI 솔루션
- **HostObject**: C++ object 가 JS prototype 처럼 expose.
- **Sync calls**: 매 native function 의 즉시 invoke (no queue).
- **Shared memory (ArrayBuffer)**: 매 zero-copy data exchange.
- **TurboModules**: JSI 기반 native module — lazy-loaded, typed.
- **Fabric**: JSI 기반 renderer — synchronous layout.
### 매 응용
1. Reanimated 3+: UI thread 에서 매 worklet 의 sync 실행 (60→120Hz).
2. MMKV: native key-value store 의 sync access (AsyncStorage 의 100x).
3. VisionCamera: frame processor 의 native↔JS 의 zero-copy.
## 💻 패턴
### TurboModule — JS 측 spec
```typescript
// NativeCalculator.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
add(a: number, b: number): number; // 매 sync return
sha256(input: string): Promise<string>;
getConstants(): { version: string };
}
export default TurboModuleRegistry.getEnforcing<Spec>('Calculator');
```
### TurboModule — iOS 구현
```objc
// Calculator.mm
#import "Calculator.h"
#import <RNCalculatorSpec/RNCalculatorSpec.h>
@implementation Calculator
RCT_EXPORT_MODULE()
- (NSNumber *)add:(double)a b:(double)b {
return @(a + b); // 매 sync, no bridge
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeCalculatorSpecJSI>(params);
}
@end
```
### HostObject — direct C++ binding
```cpp
// MyHostObject.cpp
#include <jsi/jsi.h>
using namespace facebook::jsi;
class MyHostObject : public HostObject {
public:
Value get(Runtime& rt, const PropNameID& name) override {
auto n = name.utf8(rt);
if (n == "fastFunction") {
return Function::createFromHostFunction(
rt, name, 1,
[](Runtime& rt, const Value&, const Value* args, size_t) -> Value {
double x = args[0].asNumber();
return Value(x * 2.0); // 매 sync, no JSON
});
}
return Value::undefined();
}
};
void install(Runtime& rt) {
auto obj = std::make_shared<MyHostObject>();
rt.global().setProperty(rt, "myNative", Object::createFromHostObject(rt, obj));
}
```
### Reanimated worklet (JSI 활용)
```typescript
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function Box() {
const x = useSharedValue(0); // 매 UI thread shared
const style = useAnimatedStyle(() => ({
transform: [{ translateX: x.value }],
})); // 매 worklet, UI thread 에서 sync 실행
return (
<Animated.View style={style} onTouchEnd={() => {
x.value = withSpring(100); // 매 JSI 의 sync update
}} />
);
}
```
### MMKV (sync storage)
```typescript
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('user.id', '123'); // 매 sync, no Promise
const id = storage.getString('user.id'); // 매 sync
// AsyncStorage: await storage.getItem(...) — 매 ms 단위 latency
```
### VisionCamera frame processor
```typescript
import { useFrameProcessor } from 'react-native-vision-camera';
import { detectFaces } from 'vision-camera-face-detector';
function Scanner() {
const frameProcessor = useFrameProcessor((frame) => {
'worklet';
const faces = detectFaces(frame); // 매 native, zero-copy
runOnJS(setFaces)(faces);
}, []);
return <Camera frameProcessor={frameProcessor} />;
}
```
### Fabric component (synchronous layout)
```typescript
// MyViewNativeComponent.ts
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { ViewProps } from 'react-native';
interface NativeProps extends ViewProps {
color?: string;
}
export default codegenNativeComponent<NativeProps>('MyView');
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| RN 0.68+ 새 project | New Architecture (Fabric+TurboModules) enable |
| Sync native 필요 | TurboModule + JSI |
| Animation 60+ FPS | Reanimated 3 worklet |
| Storage sync | MMKV (JSI) over AsyncStorage |
| Frame-by-frame ML | VisionCamera + JSI worklet |
**기본값**: New Architecture, TurboModules, Reanimated 3.
## 🔗 Graph
- 부모: [[React-Native]] · [[Hermes]]
- 변형: [[Fabric]] · [[TurboModules]]
- 응용: [[MMKV]]
- Adjacent: [[New-Architecture]]
## 🤖 LLM 활용
**언제**: RN 의 native module 작성, performance bottleneck 진단, sync API 필요.
**언제 X**: 매 plain JS-only feature, Expo Go 환경 (custom native 의 X).
## ❌ 안티패턴
- **JSI sync function 에서 heavy work**: JS thread block — 매 worklet / native thread 사용.
- **HostObject 의 reference 누수**: shared_ptr cycle — Runtime invalidate 시 crash.
- **Old bridge + JSI 혼용**: serialization overhead 잔존, 매 New Architecture full migration.
- **Reanimated worklet 에서 closure capture 무분별**: 매 'worklet' directive 누락 시 silent fall-back to JS thread.
## 🧪 검증 / 중복
- Verified (reactnative.dev/architecture, RN 0.76 New Architecture default 발표).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — JSI + TurboModules 패턴 정리 |
@@ -0,0 +1,225 @@
---
id: wiki-2026-0508-javascript-optimization-patterns
title: JavaScript Optimization Patterns
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [JS Performance Patterns, V8 Optimization, JS Hot Path]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [javascript, performance, v8, optimization]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: javascript
framework: v8
---
# JavaScript Optimization Patterns
## 매 한 줄
> **"매 hot path 의 type stability + allocation 최소화"**. V8/JSC/SpiderMonkey 모두 매 inline cache + hidden class 의 의존 — 매 monomorphic call site 가 매 fastest. 매 micro-optimization 보다 매 algorithmic / batching 이 매 win 큼, 매 그러나 hot loop 에서는 매 GC pressure 의 의식 필수.
## 매 핵심
### 매 V8 의 fast path
- **Hidden class (Map)**: object shape — 매 동일 property 순서 의 같은 hidden class.
- **Inline cache (IC)**: call site 별 type 기록 — monomorphic > polymorphic > megamorphic.
- **TurboFan / Maglev**: hot function 의 optimizing compile, type feedback 기반.
- **Sparkplug** (2021+): 매 baseline JIT, 매 quick startup.
### 매 GC pressure
- **Young generation (Scavenger)**: 매 short-lived alloc — 매 cheap.
- **Old generation (Mark-Compact)**: 매 promoted object — 매 stop-the-world 의 risk.
- **Strategy**: 매 hot loop 에서 alloc 의 회피 (object pooling, typed arrays).
### 매 응용
1. Animation / game loop 의 60+ FPS 유지.
2. 매 large data transformation (millions of records).
3. Server-side hot path (Node, Bun).
## 💻 패턴
### Object shape stability
```javascript
// X — hidden class 가 다름 (assignment 순서)
function A() { this.x = 1; this.y = 2; }
function B() { this.y = 2; this.x = 1; }
const arr = [new A(), new B()]; // 매 polymorphic IC
// O — 매 동일 shape
class Point { constructor(x, y) { this.x = x; this.y = y; } }
const arr2 = [new Point(1, 2), new Point(3, 4)];
```
### Monomorphic call site
```javascript
function add(a, b) { return a + b; } // V8 의 type feedback 기록
// X — megamorphic (string + number + array)
add(1, 2); add('a', 'b'); add([], []);
// O — monomorphic (always number)
for (let i = 0; i < 1e6; i++) add(i, i + 1);
```
### Typed arrays (no boxing)
```javascript
// X — 매 Array of number — boxed double, GC pressure
const heights = new Array(1_000_000);
for (let i = 0; i < heights.length; i++) heights[i] = Math.random();
// O — Float64Array — flat, no boxing
const heights2 = new Float64Array(1_000_000);
for (let i = 0; i < heights2.length; i++) heights2[i] = Math.random();
```
### Object pooling (hot loop)
```javascript
class Vec3Pool {
#pool = [];
acquire(x = 0, y = 0, z = 0) {
const v = this.#pool.pop() ?? { x: 0, y: 0, z: 0 };
v.x = x; v.y = y; v.z = z;
return v;
}
release(v) { this.#pool.push(v); }
}
const pool = new Vec3Pool();
function frame() {
const tmp = pool.acquire(1, 2, 3);
// ... compute ...
pool.release(tmp);
}
```
### Loop hoisting
```javascript
// X — length 매 iter 마다 access
for (let i = 0; i < items.length; i++) { ... }
// O — modern V8 의 자동 hoist 하지만 매 explicit 가 안전
const n = items.length;
for (let i = 0; i < n; i++) { ... }
// O+ — for-of with iterator (매 modern, V8 fast path)
for (const it of items) { ... }
```
### Map vs object lookup
```javascript
// 매 string key 의 dynamic — Map 의 매 fast & predictable
const cache = new Map();
cache.set(key, value);
cache.get(key);
// 매 known small fixed keys — object 매 inline cache 의 winning
const config = { mode: 'fast', retries: 3 };
```
### Avoid `arguments`, use rest
```javascript
// X — arguments 의 deopt (non-array, function-bound)
function sum() {
let s = 0;
for (let i = 0; i < arguments.length; i++) s += arguments[i];
return s;
}
// O
function sum(...nums) {
let s = 0;
for (const n of nums) s += n;
return s;
}
```
### Batching DOM / state updates
```javascript
// X — 매 update 의 layout thrash
items.forEach(it => container.appendChild(make(it)));
// O — DocumentFragment batch
const frag = document.createDocumentFragment();
items.forEach(it => frag.appendChild(make(it)));
container.appendChild(frag);
```
### Web Workers (off-main-thread)
```javascript
// main.js
const worker = new Worker('worker.js', { type: 'module' });
worker.postMessage({ data: bigArray }, [bigArray.buffer]); // 매 transfer, zero-copy
// worker.js
self.onmessage = (e) => {
const result = heavy(e.data.data);
self.postMessage(result);
};
```
### Structured cloning vs transferable
```javascript
// 매 1MB+ data — transferable 의 50-100x faster
const buf = new ArrayBuffer(1_000_000);
worker.postMessage(buf, [buf]); // 매 ownership transfer
```
### Lazy evaluation (generator)
```javascript
function* range(from, to) {
for (let i = from; i < to; i++) yield i;
}
function* map(it, f) { for (const v of it) yield f(v); }
function* filter(it, p) { for (const v of it) if (p(v)) yield v; }
// 매 1B element 의 first 10 — alloc 의 X
const result = [];
for (const v of filter(map(range(0, 1e9), x => x * 2), x => x % 3 === 0)) {
result.push(v);
if (result.length === 10) break;
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Hot loop alloc | typed array + object pool |
| Large data transform | streaming / generator |
| Heavy compute | Web Worker + transferable |
| DOM batch | DocumentFragment / requestAnimationFrame |
| Type stability 의심 | DevTools Profiler "ICs" tab |
| 매 micro-bench 결과 의 의심 | always profile in production-like build |
**기본값**: 매 algorithmic win 우선, 매 hot path 만 micro-optimize.
## 🔗 Graph
- 부모: [[JavaScript]] · [[V8]]
- 변형: [[JSC]]
- 응용: [[Web Worker (웹 워커)|Web-Workers]] · [[Animation-Performance]]
- Adjacent: [[Garbage-Collection]]
## 🤖 LLM 활용
**언제**: 매 measurable bottleneck 진단 후, hot loop 작성, large data transform.
**언제 X**: 매 cold path / one-shot script (premature optimization), 매 readability cost > perf gain.
## ❌ 안티패턴
- **Premature micro-opt**: 매 readability 희생, 매 measure 없이 추측.
- **`delete obj.prop`**: hidden class transition 유발, IC deopt.
- **Hot loop 의 closure alloc**: `arr.map(x => x*2)` 매 frame — 매 함수 hoist.
- **`try/catch` in hot loop**: 매 V8 의 optimization 일부 비활성 (개선되었지만 여전히 cost).
- **string concat in loop**: `+=` 매 hot loop — 매 array.join.
## 🧪 검증 / 중복
- Verified (V8 blog, Chrome DevTools Performance docs, Bun benchmark methodology).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — V8 hot-path optimization 패턴 정리 |
@@ -0,0 +1,154 @@
---
id: wiki-2026-0508-judgment
title: Judgment
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Engineering Judgment, Trade-off Judgment, Frontend Judgment]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [judgment, engineering, decision-making, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: meta
framework: meta
---
# Judgment
## 매 한 줄
> **"매 좋은 frontend 는 매 framework choice 가 아니라 매 trade-off 의 매 명시적 분석에서 나온다."**. Performance / DX / accessibility / bundle size / SEO 의 매 weight 가 매 product context 에 따라 매 다르다. Judgment 는 매 가르칠 수 있는 skill — checklist + heuristic.
## 매 핵심
### 매 Trade-off 축
- **Performance vs DX**: SSR + island vs CSR SPA.
- **Bundle vs Feature**: tree-shake-able lib vs all-in-one.
- **Type safety vs Velocity**: strict TS vs JS prototyping.
- **A11y vs Custom**: native element vs custom component.
- **Cache vs Freshness**: SWR vs no-store.
### 매 Heuristic
1. **Native first**: 매 platform primitive 가 매 free win.
2. **Measure before optimize**: 매 Lighthouse / RUM 먼저.
3. **Latency budget**: 매 LCP < 2.5s, INP < 200ms, CLS < 0.1 (Core Web Vitals 2026).
4. **Bundle budget**: 매 route 당 매 100KB gzip.
5. **Dependency cost**: 매 add 마다 매 maintenance + bundle + supply chain risk.
### 매 Decision framework
- **Reversible vs One-way door**: framework choice = one-way. 매 careful.
- **Time horizon**: 매 6 개월 vs 매 5 년 — 매 다른 답.
- **Team skill**: 매 unfamiliar tech 의 매 hidden cost.
### 매 응용
1. SSR vs CSR — 매 SEO / TTI / DX matrix.
2. State manager 선택 — 매 server vs client state 분리.
3. CSS strategy — 매 atomic / module / runtime.
## 💻 패턴
### Decision matrix template (Markdown)
```markdown
| 기준 | Option A | Option B | Weight |
|---|---|---|---|
| Bundle size | 12KB | 45KB | 0.3 |
| DX | High | Medium | 0.2 |
| Maintenance | Active | Stale | 0.3 |
| TS support | Native | Patchy | 0.2 |
| **Score** | 0.84 | 0.51 | — |
```
### Bundle budget enforcement (CI)
```javascript
// vite.config.ts
export default {
build: {
rollupOptions: {
output: { manualChunks: { vendor: ['react', 'react-dom'] } },
},
chunkSizeWarningLimit: 100, // KB
},
};
// Then: bundlesize / size-limit in CI
```
### Performance budget gate
```yaml
# .github/workflows/perf.yml
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
https://staging.example.com/
budgetPath: ./budget.json
assertions: |
categories.performance: ['error', { minScore: 0.9 }]
audits.largest-contentful-paint: ['error', { maxNumericValue: 2500 }]
audits.interaction-to-next-paint: ['error', { maxNumericValue: 200 }]
```
### Feature flag for risky migration
```typescript
import { useFlag } from '@/lib/flags';
function Page() {
const useNewRouter = useFlag('new-router-2026');
return useNewRouter ? <NextAppRouter /> : <PagesRouter />;
}
```
### A11y default vs custom
```tsx
// PREFER native
<button onClick={...}>Save</button>
// Avoid custom unless necessary
<div role="button" tabIndex={0} onKeyDown={handle}>...</div>
```
### Reversibility check
```markdown
- [ ] Can we roll back in 1 day? (file flag, env var)
- [ ] Can we roll back in 1 week? (revert PR)
- [ ] One-way door? (DB schema, public API) — 매 RFC 필수
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| 매 SEO 우선 | SSR / SSG (Next.js, Astro, SvelteKit) |
| 매 dashboard (auth-only) | CSR SPA (Vite + React) |
| 매 content site | Astro / 11ty (zero JS default) |
| 매 realtime collaborative | CSR + WebSocket / CRDT |
| 매 mobile app | RN / Expo (Hermes) |
| 매 desktop app | Tauri (Rust) > Electron |
**기본값**: 매 measure → matrix → smallest reversible step.
## 🔗 Graph
- 변형: [[ADR]]
- 응용: [[Large_Frontend_Projects|Frontend Architecture]]
- Adjacent: [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]] · [[Accessibility (A11y)|Accessibility]] · [[DX]]
## 🤖 LLM 활용
**언제**: 매 framework / library 선택 시, 매 RFC 작성, 매 trade-off 명시화.
**언제 X**: 매 obvious choice — 매 over-engineering.
## ❌ 안티패턴
- **Resume-driven development**: 매 새로운 tech 만 추구.
- **Unmeasured optimization**: 매 perf 직감 만.
- **Cargo cult**: 매 big company 가 사용 = 매 우리에게 맞다 X.
- **Analysis paralysis**: 매 endless matrix — 매 timebox.
## 🧪 검증 / 중복
- Verified (Core Web Vitals 2026 thresholds, ADR template).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — 매 trade-off matrix + budget pattern |
@@ -0,0 +1,209 @@
---
id: wiki-2026-0508-memory-leak-debugging-in-javascr
title: Memory Leak Debugging in JavaScript
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [JS Memory Leak, Heap Leak Debugging]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [javascript, performance, debugging, memory]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript
framework: Chrome DevTools
---
# Memory Leak Debugging in JavaScript
## 매 한 줄
> **"매 unintended retention — 매 GC 매 reach 가능한 reference chain 매 끊지 못해 매 heap 매 grows unbounded"**. JS 매 mark-and-sweep GC 자동이지만 매 closure/listener/global/timer 매 long-lived reference 매 object lifecycle 매 의도와 분리시키면 매 leak 발생, 매 Chrome DevTools Heap Snapshot 매 진단 standard.
## 매 핵심
### 매 leak sources (top 5)
- **Detached DOM nodes**: 매 element removed from tree 매 JS reference 잔존.
- **Event listeners**: 매 addEventListener 매 removeEventListener 없이 매 component unmount.
- **Timers**: setInterval/setTimeout 매 cleanup 누락 매 closure 매 모두 retain.
- **Closures**: outer scope variables 매 inner function 매 capture 후 매 long-lived.
- **Global accumulation**: window/globalThis 매 cache/array 매 unbounded push.
### 매 detection tools
- **Chrome DevTools Memory**: Heap snapshot, allocation timeline, allocation sampling.
- **performance.measureUserAgentSpecificMemory()** (Chrome 89+): 매 cross-origin isolated context.
- **Node.js**: --inspect + Chrome DevTools, heapdump module, --heap-prof flag.
- **WeakRef + FinalizationRegistry**: 매 GC 관찰 (debugging only).
### 매 응용
1. SPA 매 route navigation 매 retain leak 진단.
2. Long-running dashboard 매 hour-scale leak 감시.
3. Node.js server 매 RSS growth 매 root cause.
4. React/Vue component lifecycle leak detection.
## 💻 패턴
### Heap snapshot 3-snapshot technique
```
1. App 초기 load → Snapshot 1 (baseline)
2. Suspect action 수행 (modal open/close ×10) → Snapshot 2
3. 동일 action 재수행 → Snapshot 3
4. Snapshot 3 의 Comparison → Snapshot 1
5. "Allocated between snapshots 1 and 3" 의 still-alive objects = leak
```
### Detached DOM 탐색 (DevTools Console)
```js
// Heap snapshot Class filter:
// "Detached HTMLDivElement"
// "Detached HTMLElement"
// 매 instance 매 retainer chain 매 inspect — 매 root retainer 매 leak 출처
```
### Event listener leak — fix pattern
```js
// 매 BAD
class Widget {
constructor() {
window.addEventListener('resize', this.onResize.bind(this));
}
onResize() { /* ... */ }
destroy() { /* listener still attached */ }
}
// 매 GOOD
class Widget {
constructor() {
this.onResize = this.onResize.bind(this);
window.addEventListener('resize', this.onResize);
}
onResize() { /* ... */ }
destroy() {
window.removeEventListener('resize', this.onResize);
}
}
```
### AbortController 매 modern cleanup
```js
class Component {
constructor() {
this.ac = new AbortController();
const { signal } = this.ac;
window.addEventListener('scroll', this.onScroll, { signal });
window.addEventListener('resize', this.onResize, { signal });
fetch('/api', { signal });
}
destroy() {
this.ac.abort(); // 매 모든 listener + fetch 매 한 번에 cleanup
}
}
```
### Timer leak fix
```js
// 매 BAD — closure captures large data
function startPolling(bigData) {
setInterval(() => {
console.log(bigData.length); // bigData retained forever
}, 1000);
}
// 매 GOOD — explicit handle + cleanup
const handle = setInterval(poll, 1000);
function stop() { clearInterval(handle); }
```
### WeakMap 매 cache without leak
```js
// 매 BAD — Map 매 key 매 GC X
const cache = new Map();
function getMeta(node) {
if (!cache.has(node)) cache.set(node, computeMeta(node));
return cache.get(node); // node removed from DOM but still in cache
}
// 매 GOOD — WeakMap key 매 GC 가능
const cache = new WeakMap();
function getMeta(node) {
if (!cache.has(node)) cache.set(node, computeMeta(node));
return cache.get(node);
}
```
### performance.measureUserAgentSpecificMemory
```js
// crossOriginIsolated context (COOP+COEP headers) 필요
if (crossOriginIsolated && performance.measureUserAgentSpecificMemory) {
const result = await performance.measureUserAgentSpecificMemory();
console.log('bytes:', result.bytes);
console.table(result.breakdown);
}
```
### FinalizationRegistry 매 GC 관찰 (debug only)
```js
const registry = new FinalizationRegistry((tag) => {
console.log(`GC'd: ${tag}`);
});
class Tracked {
constructor(name) {
registry.register(this, name);
}
}
new Tracked('widget-1'); // → "GC'd: widget-1" eventually (or never)
```
### Node.js heap snapshot
```bash
node --inspect server.js
# 매 chrome://inspect → Memory → Take heap snapshot
# 또는 programmatic:
```
```js
import { writeHeapSnapshot } from 'node:v8';
const path = writeHeapSnapshot(); // .heapsnapshot file
console.log(`Snapshot: ${path}`);
```
## 매 결정 기준
| 상황 | Tool/Approach |
|---|---|
| Browser SPA growing memory | DevTools Heap Snapshot 3-snapshot |
| 매 frame allocation hotspot | Allocation timeline (sampling) |
| Detached DOM 의심 | Class filter "Detached " in snapshot |
| Node.js RSS growth | writeHeapSnapshot + Chrome DevTools |
| Continuous monitoring (production) | performance.measureUserAgentSpecificMemory |
| Event listener leak | AbortController 매 unified cleanup |
**기본값**: 매 Heap Snapshot 3-snapshot diff — 매 retainer chain 매 따라 root 매 식별.
## 🔗 Graph
- 부모: [[Garbage-Collection]]
- Adjacent: [[Chrome-DevTools]] · [[AbortController]]
## 🤖 LLM 활용
**언제**: 매 SPA/long-running app 의 메모리 증가, 매 unmount 후 referent 잔존, 매 production memory metrics 의 anomaly.
**언제 X**: 매 short-lived script (CLI tool), 매 GC pause 문제 (different — GC tuning territory).
## ❌ 안티패턴
- **`delete` keyword 의존**: 매 reference 매 nullify 안 함 — 매 다른 reference 매 retain.
- **`window.gc()` 매 production**: 매 only with --expose-gc flag, 매 hint 일 뿐.
- **Allocation timeline 매 production trace**: 매 overhead 매 큼 — 매 staging 에서.
- **One-snapshot 진단**: 매 baseline 없으면 매 noise 와 leak 매 구분 불가.
- **DevTools 매 incognito 가정**: 매 extension 매 heap pollution — 매 incognito + 매 disabled extensions.
## 🧪 검증 / 중복
- Verified (Chrome DevTools docs, V8 blog, Node.js v8 module).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — leak source taxonomy + DevTools workflow + AbortController/WeakMap patterns |
@@ -0,0 +1,263 @@
---
id: wiki-2026-0508-micro-frontends
title: Micro frontends
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [MFE, Micro-frontend Architecture]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, architecture, modular]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Module Federation / Single-SPA
---
# Micro frontends
## 매 한 줄
> **"매 microservices 매 frontend 적용 — 매 large UI 매 independent deployable team-owned vertical slices 매 분할"**. ThoughtWorks Tech Radar (2016) 매 이름 채택, 매 Webpack 5 Module Federation (2020) 매 mainstream 진입, 매 2026 매 large enterprise frontend (Spotify, IKEA, DAZN) 매 standard, 매 Vite Module Federation + Native Federation 매 modern stack.
## 매 핵심
### 매 핵심 가치 (vs monolith)
- **Independent deploy**: 매 team 매 자기 slice 매 release pipeline.
- **Tech heterogeneity**: 매 React + Vue + Svelte 매 공존 (단 cost ↑).
- **Team scalability**: 매 vertical team ownership — 매 Conway's law 매 align.
- **Incremental migration**: 매 legacy → modern 매 점진적 교체.
### 매 cost
- **Bundle duplication**: 매 framework 매 여러 번 load.
- **Cross-MFE state**: 매 shared store 매 design.
- **Routing coordination**: 매 shell + child route 매 sync.
- **Versioning skew**: 매 shared dep 매 version conflict.
- **DX overhead**: 매 dev 매 multi-repo / multi-server.
### 매 composition strategies
- **Build-time**: npm package — 매 monorepo + 매 publish (closest to monolith).
- **Server-side**: SSI/ESI/Tailor — 매 edge composition (Podium, Mosaic).
- **Run-time iframe**: 매 ultimate isolation — 매 worst UX.
- **Run-time Web Components**: 매 framework-agnostic embed.
- **Run-time Module Federation** (Webpack 5 / Vite): 매 sharing dep + dynamic import — 매 dominant 2026.
- **Run-time Single-SPA**: 매 lifecycle orchestration framework.
### 매 응용
1. Large e-commerce (header/cart/product/checkout 매 별도 team).
2. Multi-tenant SaaS (workspace + apps marketplace).
3. Legacy modernization (strangler fig pattern).
4. White-label platforms (per-customer customization).
## 💻 패턴
### Webpack Module Federation — host (shell)
```js
// shell/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
cart: 'cart@https://cart.example.com/remoteEntry.js',
product: 'product@https://product.example.com/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
}
```
### Module Federation — remote (cart MFE)
```js
// cart/webpack.config.js
new ModuleFederationPlugin({
name: 'cart',
filename: 'remoteEntry.js',
exposes: {
'./CartWidget': './src/CartWidget',
'./useCart': './src/hooks/useCart',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
})
```
### Host consume (React lazy)
```tsx
// shell/App.tsx
import React, { lazy, Suspense } from 'react'
const CartWidget = lazy(() => import('cart/CartWidget'))
export function App() {
return (
<div>
<Header />
<Suspense fallback={<div>Loading cart</div>}>
<CartWidget />
</Suspense>
</div>
)
}
```
### Vite Module Federation
```ts
// cart/vite.config.ts
import federation from '@originjs/vite-plugin-federation'
export default {
plugins: [
federation({
name: 'cart',
filename: 'remoteEntry.js',
exposes: { './CartWidget': './src/CartWidget.tsx' },
shared: ['react', 'react-dom'],
}),
],
build: { target: 'esnext', minify: false, cssCodeSplit: true },
}
```
### Single-SPA root config
```ts
// root-config/index.ts
import { registerApplication, start } from 'single-spa'
registerApplication({
name: '@org/cart',
app: () => System.import('@org/cart'),
activeWhen: ['/cart'],
})
registerApplication({
name: '@org/product',
app: () => System.import('@org/product'),
activeWhen: ['/products'],
})
start()
```
### Single-SPA child lifecycle
```ts
// cart-app/src/main.ts
import { h, createApp } from 'vue'
import singleSpaVue from 'single-spa-vue'
import Root from './Root.vue'
const lifecycles = singleSpaVue({
createApp,
appOptions: { render: () => h(Root) },
})
export const bootstrap = lifecycles.bootstrap
export const mount = lifecycles.mount
export const unmount = lifecycles.unmount
```
### Web Components 매 framework-agnostic embed
```ts
// cart-mfe.ts
class CartElement extends HTMLElement {
connectedCallback() {
const root = this.attachShadow({ mode: 'open' })
// mount React/Vue/Svelte into shadow DOM
mountReact(<CartWidget />, root)
}
disconnectedCallback() { unmountReact(this.shadowRoot!) }
}
customElements.define('app-cart', CartElement)
```
```html
<!-- 매 host page (any framework) -->
<script src="https://cart.example.com/cart.js" type="module"></script>
<app-cart></app-cart>
```
### Cross-MFE communication — Custom Events
```ts
// 매 publish
window.dispatchEvent(new CustomEvent('cart:item-added', {
detail: { sku: 'A123', qty: 1 },
}))
// 매 subscribe
window.addEventListener('cart:item-added', (e) => {
console.log('item added:', (e as CustomEvent).detail)
})
```
### Shared shell store (BroadcastChannel)
```ts
const channel = new BroadcastChannel('app-state')
// MFE A
channel.postMessage({ type: 'auth/login', user })
// MFE B
channel.onmessage = ({ data }) => {
if (data.type === 'auth/login') updateLocalState(data.user)
}
```
### CSS isolation strategies
```
1. Shadow DOM (Web Components 매 자동 scope)
2. CSS Modules (build-time hash)
3. CSS-in-JS (runtime scope, e.g., styled-components)
4. PostCSS namespace plugin (legacy)
5. Tailwind prefix per MFE: `tw-cart-`, `tw-product-`
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Single team, single product | Monolith (MFE overkill) |
| Multiple teams, shared shell | Module Federation |
| Legacy → React migration | iframe → Web Components → MF |
| Framework heterogeneity | Web Components / Single-SPA |
| Maximum isolation (security) | iframe (last resort) |
| Tight budget (LCP critical) | Build-time composition |
| SSR + MFE | Server-side composition (Tailor, Mosaic) |
| Vite stack | Native Federation / vite-plugin-federation |
**기본값**: 매 React/Vue 균일 stack 매 Module Federation, 매 framework 혼재 매 Single-SPA + Web Components.
## 🔗 Graph
- 부모: [[프론트엔드_및_UIUX_표준|Frontend-Architecture]] · [[Microservices]]
- 변형: [[Module-Federation]] · [[Web-Components]]
- Adjacent: [[Monorepo]] · [[Vite]]
## 🤖 LLM 활용
**언제**: 매 large team (50+ FE devs), 매 independent deploy 필요, 매 legacy modernization, 매 multi-tenant white-label.
**언제 X**: 매 small team (<10), 매 single-page simple SPA, 매 LCP-critical landing — 매 monolith 적합.
## ❌ 안티패턴
- **매 component-level MFE**: 매 button 매 MFE — 매 latency overhead — 매 vertical slice 매 단위.
- **Shared global state via window**: 매 race condition + 매 hidden coupling.
- **Framework version skew**: 매 React 17 + 18 매 host — 매 hooks 깨짐.
- **No design system**: 매 visual inconsistency — 매 shared tokens/components 필수.
- **Synchronous load chain**: 매 cascade waterfall — 매 lazy + 매 parallel 매 host.
- **MFE 매 small team 매 도입**: 매 over-engineering — 매 monolith + 매 module boundary 매 충분.
## 🧪 검증 / 중복
- Verified (ThoughtWorks Tech Radar, micro-frontends.org, Module Federation docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — composition strategies + MF/Single-SPA/WC patterns + comm matrix |
@@ -0,0 +1,200 @@
---
id: wiki-2026-0508-nextjs-framework
title: Next.js Framework
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Next.js, NextJS, Nextjs]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
verification_status: applied
tags: [nextjs, react, ssr, framework, vercel]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Next.js 15
---
# Next.js Framework
## 매 한 줄
> **"매 Next.js 는 React-based full-stack framework — App Router (RSC) + Server Actions + Turbopack 의 조합"**. 2026 의 Next 15 는 매 React 19 RSC + Partial Prerendering (PPR) 안정화 + Turbopack 가 default. 매 Vercel 의 commercial backing.
## 매 핵심
### 매 Render 모드 (App Router)
- **Static (default)**: 매 build-time, ISR 가능.
- **Dynamic**: 매 request-time, `cookies()` / `headers()` 사용 시 자동 전환.
- **Streaming SSR**: 매 RSC + Suspense — 매 progressive HTML.
- **PPR (Partial Prerendering)**: 매 static shell + dynamic holes — 매 Next 15 default.
### 매 핵심 API
- **Server Components**: 매 default — 매 zero JS 전송.
- **`"use client"`**: 매 client component boundary.
- **Server Actions** (`"use server"`): 매 form / mutation, RPC 자동.
- **`fetch()` extension**: 매 cache, revalidate, tags.
### 매 응용
1. E-commerce (Shopify Hydrogen 영향).
2. Marketing site + blog (ISR 강점).
3. SaaS dashboard (RSC 로 bundle 감소).
4. AI chat UI — 매 streaming Server Action.
## 💻 패턴
### Server Component data fetch
```tsx
// app/posts/page.tsx — 매 server component, async 가능
async function PostsPage() {
const posts = await fetch("https://api.example.com/posts", {
next: { revalidate: 60, tags: ["posts"] },
}).then((r) => r.json());
return (
<ul>
{posts.map((p) => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
export default PostsPage;
```
### Server Action (form mutation)
```tsx
// app/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function createPost(formData: FormData) {
await db.post.create({ data: { title: formData.get("title") as string } });
revalidateTag("posts");
}
// app/new/page.tsx
import { createPost } from "../actions";
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Save</button>
</form>
);
}
```
### Streaming with Suspense
```tsx
import { Suspense } from "react";
export default function Page() {
return (
<>
<Header /> {/* 매 즉시 stream */}
<Suspense fallback={<Skeleton />}>
<SlowProductList /> {/* 매 fetch 끝나면 stream-in */}
</Suspense>
</>
);
}
```
### Partial Prerendering (Next 15)
```tsx
// next.config.js
module.exports = {
experimental: { ppr: "incremental" },
};
// app/page.tsx — static shell + dynamic part
export const experimental_ppr = true;
import { cookies } from "next/headers";
async function CartCount() {
const session = (await cookies()).get("session");
// 매 dynamic — static shell 의 hole 로 stream
return <span>{await getCartCount(session?.value)}</span>;
}
export default function Page() {
return (
<>
<StaticHeader />
<Suspense fallback="0"><CartCount /></Suspense>
</>
);
}
```
### Route Handler (REST endpoint)
```ts
// app/api/users/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const users = await db.user.findMany();
return NextResponse.json(users);
}
export async function POST(req: Request) {
const body = await req.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}
```
### Middleware (auth gate)
```ts
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const token = req.cookies.get("token");
if (!token && req.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", req.url));
}
}
export const config = { matcher: ["/dashboard/:path*"] };
```
### Image optimization
```tsx
import Image from "next/image";
<Image src="/hero.jpg" width={1200} height={600} alt="" priority />
// 매 자동 AVIF/WebP, srcset, lazy
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Marketing site | Static + ISR |
| User dashboard | RSC + Server Action |
| Realtime chat | "use client" + WebSocket |
| Mostly static + cart | PPR (Next 15) |
| Pure SPA | 매 Next 대신 Vite + React |
**기본값**: App Router + RSC + PPR. Pages Router 는 매 legacy.
## 🔗 Graph
- 부모: [[React]]
- 변형: [[Remix]] · [[Astro]] · [[SvelteKit]]
- Adjacent: [[React Server Components]] · [[Turbopack]] · [[Edge Runtime]]
## 🤖 LLM 활용
**언제**: App Router migration, RSC 설계, Server Action 구현, PPR 적용.
**언제 X**: 매 pure backend (Express/Fastify), pure SPA.
## ❌ 안티패턴
- **`"use client"` on root layout**: 매 RSC 효과 무효 — 매 leaf 만.
- **`fetch` 안에 secret**: 매 client 로 leak 가능 — 매 Server Action 사용.
- **getInitialProps**: 매 Pages Router legacy — 매 App Router 에서 X.
- **Massive client bundle**: 매 Server Component 로 옮길 것.
## 🧪 검증 / 중복
- Verified (nextjs.org docs, React 19 RSC spec).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Next 15 RSC + PPR + Server Action |
@@ -0,0 +1,239 @@
---
id: wiki-2026-0508-nominal-typing-in-typescript
title: Nominal Typing in TypeScript
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Branded Types, Opaque Types TypeScript]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, type-system, branding]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: TypeScript 5.x
---
# Nominal Typing in TypeScript
## 매 한 줄
> **"매 TypeScript 매 structural-type 시스템 매 nominal 효과 매 simulation — 매 phantom field 매 'brand' 매 통해 매 동일 shape 매 다른 type 으로 separate"**. TS 매 native nominal X 매 (Java/C# 의 `class A {} class B {}` 매 다름) — 매 brand pattern 매 community-standard, 매 ID/Currency/Email 매 같은 매 primitive 매 mix-up bug 매 compile-time 차단.
## 매 핵심
### 매 Structural vs Nominal
- **Structural** (TS, Go, OCaml): 매 shape 매 동일하면 매 동일 type — `interface A { x: number }``interface B { x: number }`.
- **Nominal** (Java, C#, Rust): 매 declared name 매 동일해야 — 매 동일 shape 매도 매 다른 type 이면 incompatible.
### 매 brand pattern 종류
- **Class private field**: 매 `#brand` — 매 nominal 한 hint, 매 runtime cost.
- **Intersection with brand**: `T & { __brand: 'X' }` — 매 zero runtime, 매 compile-time only.
- **Symbol brand**: `T & { [brand]: 'X' }` — 매 collision-safe.
- **Unique symbol**: `declare const brand: unique symbol` — 매 strongest TS pattern.
- **TypeScript 5.0+ `type` operator** (proposed): 매 still community pattern.
### 매 응용
1. ID type separation: `UserId` vs `OrderId` 매 mix 차단.
2. Validated values: `Email`, `Url`, `NonEmptyString`.
3. Units: `Meters`, `Seconds`, `USD` vs `EUR`.
4. Sanitized strings: `SafeHtml`, `SqlEscaped`.
5. Domain primitives (DDD value object 매 lightweight 표현).
## 💻 패턴
### Basic brand
```ts
type Brand<T, B> = T & { readonly __brand: B }
type UserId = Brand<string, 'UserId'>
type OrderId = Brand<string, 'OrderId'>
const userId = 'u_123' as UserId
const orderId = 'o_456' as OrderId
function getUser(id: UserId) { /* */ }
getUser(userId) // OK
getUser(orderId) // 매 compile error: OrderId not assignable to UserId
getUser('u_123') // 매 compile error: string not assignable to UserId
```
### Smart constructor (validation)
```ts
type Email = Brand<string, 'Email'>
function makeEmail(s: string): Email | null {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s) ? (s as Email) : null
}
function sendMail(to: Email, body: string) { /* */ }
const e = makeEmail('a@b.com')
if (e) sendMail(e, 'hi') // 매 narrowed to Email
sendMail('raw' as string, 'hi') // 매 compile error
```
### Unique symbol brand (strongest)
```ts
declare const userIdBrand: unique symbol
declare const orderIdBrand: unique symbol
type UserId = string & { readonly [userIdBrand]: void }
type OrderId = string & { readonly [orderIdBrand]: void }
// 매 여기서는 매 두 type 매 절대 호환 X (unique symbol 매 collision-free)
```
### Reusable Brand utility
```ts
declare const brandSymbol: unique symbol
type Brand<T, Tag extends string> = T & { readonly [brandSymbol]: Tag }
type UserId = Brand<string, 'UserId'>
type Meters = Brand<number, 'Meters'>
type Seconds = Brand<number, 'Seconds'>
function speed(d: Meters, t: Seconds): number {
return (d as number) / (t as number)
}
const d = 100 as Meters
const t = 5 as Seconds
speed(d, t) // OK
speed(t, d) // 매 compile error
```
### Class private field (alternative)
```ts
class UserId {
#brand!: void
constructor(public readonly value: string) {}
}
class OrderId {
#brand!: void
constructor(public readonly value: string) {}
}
function getUser(id: UserId) { /* */ }
getUser(new UserId('u_1')) // OK
getUser(new OrderId('o_1')) // 매 compile error: #brand 매 only-accessible
// 매 cost: 매 wrapping object — 매 runtime allocation
```
### Sanitized string
```ts
type SafeHtml = Brand<string, 'SafeHtml'>
function escapeHtml(raw: string): SafeHtml {
return raw.replace(/[&<>"']/g, (c) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
}[c]!)) as SafeHtml
}
function render(html: SafeHtml) { /* dangerouslySetInnerHTML OK */ }
render('<script>' as SafeHtml) // 매 cast 매 강제 — 매 reviewer 매 catch
render(escapeHtml(userInput)) // 매 safe path
```
### Currency
```ts
type USD = Brand<number, 'USD'>
type EUR = Brand<number, 'EUR'>
function add(a: USD, b: USD): USD { return (a + b) as USD }
const price = 100 as USD
const tax = 7 as USD
const total = add(price, tax) // OK
const eu = 50 as EUR
add(price, eu) // 매 compile error
```
### Phantom type with generics
```ts
type Validated<T, V extends string> = T & { readonly __validated: V }
type Trimmed<T extends string> = Validated<T, 'Trimmed'>
type NonEmpty<T extends string> = Validated<T, 'NonEmpty'>
function trim<T extends string>(s: T): Trimmed<T> {
return s.trim() as Trimmed<T>
}
function requireNonEmpty<T extends string>(s: T): NonEmpty<T> {
if (s.length === 0) throw new Error('empty')
return s as NonEmpty<T>
}
```
### Library: ts-brand / Effect-TS
```ts
// 매 ts-brand
import { Brand, make } from 'ts-brand'
type UserId = Brand<string, 'UserId'>
const UserId = make<UserId>()
const id = UserId('u_1')
// 매 Effect-TS Schema
import { Schema } from '@effect/schema'
const UserId = Schema.String.pipe(Schema.brand('UserId'))
type UserId = Schema.Schema.Type<typeof UserId>
```
### JSON deserialization 매 brand 복원
```ts
interface UserJson { id: string; name: string }
interface User { id: UserId; name: string }
function fromJson(j: UserJson): User {
return { id: j.id as UserId, name: j.name }
}
// 매 boundary 매서만 cast — 매 internal 매 type 안전
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| ID separation | Brand<string, 'XId'> |
| Validated value (Email, Url) | Brand + smart constructor |
| Unit (Meters, USD) | Brand<number, 'Unit'> |
| Sanitized string | Brand<string, 'Safe'> + escape function |
| Strongest isolation | unique symbol brand |
| Runtime check 동반 | Class with #brand + factory |
| 매 library 사용 OK | ts-brand / Effect Schema |
| Domain layer 매 rich behavior | Class wrapper (DDD value object) |
**기본값**: 매 `Brand<T, 'Name'>` intersection — 매 zero runtime cost + 매 sufficient nominal effect.
## 🔗 Graph
- 부모: [[TypeScript]] · [[TypeScript 타입 시스템 (TypeScript Type System)|Type-System]]
- 변형: [[Branded-Types]] · [[Opaque-Types]]
- 응용: [[Domain-Driven-Design]] · [[Value-Object]] · [[Refinement-Type]]
- Adjacent: [[Effect-TS]] · [[Zod]] · [[Type-Safety]] · [[Smart-Constructor]]
## 🤖 LLM 활용
**언제**: 매 ID/string mix-up 매 bug surface 자주, 매 unit/currency 혼동 risk, 매 sanitized vs raw 매 boundary 강제, 매 DDD value object 매 lightweight 표현.
**언제 X**: 매 throwaway code, 매 internal-only types 매 1 곳만 사용 (overhead), 매 simple CRUD CRUD 매 brand 폭발.
## ❌ 안티패턴
- **매 모든 string 매 brand**: 매 cognitive overhead 폭증 — 매 confusion-prone boundary 만 brand.
- **`as Brand` 매 무절제 cast**: 매 brand 의 의미 없음 — 매 smart constructor 매 통과 필수.
- **Brand 매 runtime check 가정**: 매 compile-time only — 매 deserialization 매 raw cast 시 brittle.
- **매 deeply nested generic brand**: 매 type error message 매 unreadable.
- **`__brand` field 매 runtime expose**: 매 JSON serialize 시 leak — 매 phantom field 매 절대 assign X.
## 🧪 검증 / 중복
- Verified (TypeScript handbook discussion, Effect-TS Schema, ts-brand library).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — brand patterns + smart constructor + unit/currency examples |
@@ -0,0 +1,202 @@
---
id: wiki-2026-0508-one-way-data-flow
title: One-way Data Flow
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [One-way Data Flow, Unidirectional Data Flow, Top-down Props]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, frontend, architecture, data-flow, state-management]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React
---
# One-way Data Flow
## 매 한 줄
> **"매 state 매 down, event 매 up"**. Unidirectional data flow 매 React (Flux 영감) 의 core mental model — parent props 으로 child 에 데이터 전달, child callback 으로 parent 에 event 통보. 매 reasoning 의 단순화, debugging 의 traceability, time-travel 의 가능성. Vue/Solid/Svelte 도 매 동일 원칙.
## 매 핵심
### 매 핵심 규칙
- State 매 single owner (component or store).
- Owner 만 mutate 가능.
- Child 매 read-only props 만 받음.
- Child 의 변경 요청 매 callback / event / dispatch.
### 매 왜 unidirectional
- **Predictability**: state 변경 source 매 명확.
- **Debugging**: render output 매 props 의 함수.
- **Time-travel**: state snapshot 만 으로 UI 재현.
- **Concurrency**: 매 React Concurrent 매 mutable 2-way 매 deadlock-prone.
### 매 응용
1. React props/callback 패턴.
2. Redux / Zustand / Jotai store dispatch.
3. Vue `props down, emit up`.
4. Form: lift state up.
5. Event sourcing / CQRS frontend.
## 💻 패턴
### Lifting state up (React)
```tsx
function Parent() {
const [name, setName] = useState("");
return (
<>
<NameInput value={name} onChange={setName} />
<Greeting name={name} />
</>
);
}
function NameInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
function Greeting({ name }: { name: string }) {
return <p>Hello, {name || "stranger"}!</p>;
}
```
### Reducer (action up, state down)
```tsx
type Action = { type: "add"; item: string } | { type: "remove"; idx: number };
type State = { items: string[] };
function reducer(s: State, a: Action): State {
switch (a.type) {
case "add": return { items: [...s.items, a.item] };
case "remove": return { items: s.items.filter((_, i) => i !== a.idx) };
}
}
function TodoApp() {
const [state, dispatch] = useReducer(reducer, { items: [] });
return (
<>
<AddBox onAdd={(item) => dispatch({ type: "add", item })} />
<List items={state.items} onRemove={(idx) => dispatch({ type: "remove", idx })} />
</>
);
}
```
### Zustand (store as single source)
```ts
import { create } from "zustand";
interface CartStore {
items: { id: string; qty: number }[];
add: (id: string) => void;
remove: (id: string) => void;
}
export const useCart = create<CartStore>((set) => ({
items: [],
add: (id) => set((s) => ({ items: [...s.items, { id, qty: 1 }] })),
remove: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
}));
function ProductCard({ id }: { id: string }) {
const add = useCart((s) => s.add);
return <button onClick={() => add(id)}>Add</button>;
}
```
### Vue 3 props down + emit up
```vue
<!-- Parent.vue -->
<template>
<Counter :value="count" @increment="count++" />
</template>
<script setup>
import { ref } from "vue";
const count = ref(0);
</script>
<!-- Counter.vue -->
<template>
<button @click="$emit('increment')">{{ value }}</button>
</template>
<script setup>
defineProps<{ value: number }>();
defineEmits<{ increment: [] }>();
</script>
```
### Server-driven (React Query + URL state)
```tsx
import { useQuery } from "@tanstack/react-query";
import { useSearchParams } from "react-router";
function ProductList() {
const [params, setParams] = useSearchParams();
const category = params.get("category") ?? "all";
const { data } = useQuery({
queryKey: ["products", category],
queryFn: () => fetch(`/api/products?cat=${category}`).then((r) => r.json()),
});
return (
<>
<CategoryPicker value={category} onChange={(v) => setParams({ category: v })} />
{data?.map((p) => <Card key={p.id} product={p} />)}
</>
);
}
```
### Forbidden 2-way leak (anti-pattern shown)
```tsx
// ❌ child mutates parent's prop directly via mutable ref
function BadChild({ obj }: { obj: { count: number } }) {
return <button onClick={() => obj.count++}>+</button>; // parent never re-renders
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| 2-3 components share state | Lift state up |
| Cross-tree state | Context / Zustand / Redux |
| Server data | React Query / SWR (single source) |
| URL state | useSearchParams / Next router |
| Form-heavy local | useReducer + dispatch |
| Vue | props + emit (or Pinia) |
**기본값**: state 매 highest common ancestor 또는 매 store. 매 props down + callback up.
## 🔗 Graph
- 부모: [[React]]
- 변형: [[프론트엔드_및_UIUX_표준|Redux]] · [[Zustand]] · [[Pinia]]
- 응용: [[useReducer]] · [[Event-Sourcing]]
- Adjacent: [[Reactive-Streams]] · [[Signals]]
## 🤖 LLM 활용
**언제**: React/Vue/Solid component design, form lifting, store architecture.
**언제 X**: 매 highly local input (e.g. simple text field) — 매 controlled-input over-engineering 회피, uncontrolled OK.
## ❌ 안티패턴
- **Mutate props in child**: 매 silent re-render miss.
- **Shared mutable ref**: 매 React diff 의 invariant 위반.
- **Two-way binding in big tree**: 매 cycle / cascade.
- **Prop drilling 10층**: 매 Context / store 으로 cut.
- **Local form state in 매 sync 매 server**: 매 stale conflict — server 매 source of truth + optimistic update.
## 🧪 검증 / 중복
- Verified (React docs — Sharing State Between Components, Vue docs — Component Basics, Redux principles).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — lifting/reducer/Zustand/Vue emit/server-driven 패턴 |
@@ -0,0 +1,158 @@
---
id: wiki-2026-0508-opaque-types
title: Opaque Types (TypeScript)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Opaque Types, Branded Types, Nominal Types in TS]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, types, nominal-typing, branded-types]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: none
---
# Opaque Types (TypeScript)
## 매 한 줄
> **"매 structural type 의 nominal escape hatch"**. TypeScript 매 structural typing 이지만, opaque (branded) type 으로 매 string 끼리 / number 끼리 mix-up 방지. UserId vs PostId, Cents vs Dollars 등 매 domain primitive 의 type-safety 강화.
## 매 핵심
### 매 왜 필요
- 매 TS structural — `type UserId = string; type PostId = string;` 매 interchangeable.
- 매 runtime cost X 의 compile-time distinction 필요.
- 매 unit confusion (cents/dollars, ms/sec) 의 typing.
### 매 두 가지 구현
- **Symbol brand** (가장 strict, 매 declaration merging 어려움).
- **Unique string brand** (가장 ergonomic, TS 4.9+).
### 매 응용
1. ID types — UserId, PostId, OrgId 의 cross-mixing 방지.
2. Units — Meters vs Feet, Cents vs Dollars.
3. Validated strings — `Email`, `URL`, `SafeHtml` (validator 통과 후 만 cast).
4. Crypto — `HashedPassword` vs `PlainPassword`.
## 💻 패턴
### Basic branded type
```ts
declare const brand: unique symbol;
type Brand<T, B> = T & { readonly [brand]: B };
type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
const asUserId = (s: string): UserId => s as UserId;
const u: UserId = asUserId("u_123");
const p: PostId = asUserId("u_123"); // ❌ Type error
```
### Smart constructor with validation
```ts
type Email = Brand<string, "Email">;
function makeEmail(s: string): Email {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) {
throw new Error(`Invalid email: ${s}`);
}
return s as Email;
}
function send(to: Email, body: string) { /* ... */ }
send(makeEmail("a@b.co"), "hi"); // ✓
send("a@b.co" as string, "hi"); // ❌
```
### Numeric units
```ts
type Cents = Brand<number, "Cents">;
type Dollars = Brand<number, "Dollars">;
const toCents = (d: Dollars): Cents => (d * 100) as Cents;
const toDollars = (c: Cents): Dollars => (c / 100) as Dollars;
function charge(amount: Cents) { /* ... */ }
charge(toCents(9.99 as Dollars)); // ✓
charge(9.99 as Dollars); // ❌
```
### Result-style smart constructor (no throw)
```ts
type ValidUrl = Brand<string, "ValidUrl">;
function parseUrl(s: string): ValidUrl | null {
try {
new URL(s);
return s as ValidUrl;
} catch {
return null;
}
}
const u = parseUrl(input);
if (u) fetch(u); // u: ValidUrl
```
### Generic Tagged
```ts
type Tagged<T, Tag extends string> = T & { readonly __tag: Tag };
type Meters = Tagged<number, "m">;
type Seconds = Tagged<number, "s">;
const speed = (m: Meters, s: Seconds): number => m / s;
```
### Zod + brand (runtime + compile-time)
```ts
import { z } from "zod";
const EmailSchema = z.string().email().brand<"Email">();
type Email = z.infer<typeof EmailSchema>; // string & z.BRAND<"Email">
const parsed = EmailSchema.parse(input); // Email
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| ID types | unique symbol brand |
| Validated input | smart constructor (parse, don't validate) |
| Runtime + compile | Zod `.brand<T>()` |
| Numeric units | Tagged number with conversion functions |
| One-off | inline `string & { _t: "X" }` |
**기본값**: unique-symbol brand + smart constructor — 매 boundary 의 cast, 매 internal 은 type-safe propagation.
## 🔗 Graph
- 부모: [[TypeScript-Type-System]]
- 변형: [[Branded-Types]]
- 응용: [[Parse-Dont-Validate]]
- Adjacent: [[API 응답 및 상태 모델링 (State Modeling and API Responses)|Discriminated-Unions]]
## 🤖 LLM 활용
**언제**: ID 매 mix 위험 / unit 매 confusion / validated string 의 boundary protection.
**언제 X**: 매 internal helper / one-off — 매 over-engineering.
## ❌ 안티패턴
- **Brand without smart constructor**: 매 `as` cast 매 어디서나 — type 의 의미 없음.
- **Erase brand**: `JSON.stringify` → parse 매 brand 손실 — boundary 에 re-validate.
- **Brand on `any`**: 매 base type 의 narrowing 효과 X.
- **Excessive branding**: 매 internal 의 모든 string 에 brand — readability 폭락.
## 🧪 검증 / 중복
- Verified (TypeScript Handbook, Effective TypeScript Item 37, Zod docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — symbol brand, smart constructor, Zod brand, units 패턴 |
@@ -0,0 +1,188 @@
---
id: wiki-2026-0508-open-closed-principle-ocp
title: Open-Closed Principle (OCP)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [OCP, Open Closed Principle, SOLID OCP]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [solid, ocp, design-principles, oop, architecture]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: none
---
# Open-Closed Principle (OCP)
## 매 한 줄
> **"매 extension 에 open, modification 에 closed"**. Bertrand Meyer (1988) 매 originally inheritance, Robert Martin (1996) 매 abstraction-based redefinition. 새 behavior 매 *추가* 의 way 로, 매 existing code 의 *수정* 없이 — strategy / plugin / open enum 등 매 광범위 응용.
## 매 핵심
### 매 두 가지 해석
- **Meyer's OCP**: 매 inheritance — class 매 closed for modification (매 published interface 안정), open for extension (매 subclass).
- **Polymorphic OCP** (Martin): 매 abstraction (interface) 의존, 매 implementation 추가 만 으로 extend.
### 매 왜 중요
- 매 regression 위험 줄임 (existing 안 건드림).
- 매 plugin / extension model 가능.
- 매 testability — 매 mock implementation 추가 만.
### 매 응용
1. Strategy pattern — payment processor (Stripe, Toss, PayPal).
2. Discriminated union 의 add new variant.
3. Plugin architecture (Vite plugin, ESLint rule).
4. Visitor pattern.
5. Middleware chain (Koa, Express).
## 💻 패턴
### Strategy interface (TS)
```ts
interface PaymentProcessor {
charge(amount: number, token: string): Promise<{ id: string }>;
}
class StripeProcessor implements PaymentProcessor {
async charge(amount: number, token: string) {
return { id: `stripe_${Date.now()}` };
}
}
class TossProcessor implements PaymentProcessor {
async charge(amount: number, token: string) {
return { id: `toss_${Date.now()}` };
}
}
// New processor → just add new class. CheckoutService unchanged.
class CheckoutService {
constructor(private processor: PaymentProcessor) {}
pay(amt: number, t: string) { return this.processor.charge(amt, t); }
}
```
### Discriminated union + exhaustiveness
```ts
type Shape =
| { kind: "circle"; r: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.r ** 2;
case "square": return s.side ** 2;
case "triangle": return (s.base * s.height) / 2;
default: {
const _exhaust: never = s;
throw new Error(_exhaust);
}
}
}
// Add 'pentagon' → TS error in `area`, no silent miss.
```
### Plugin / hook registry
```ts
type Hook<T> = (ctx: T) => void | Promise<void>;
class HookRegistry<T> {
private hooks: Hook<T>[] = [];
add(h: Hook<T>) { this.hooks.push(h); }
async run(ctx: T) { for (const h of this.hooks) await h(ctx); }
}
const onUserCreated = new HookRegistry<{ userId: string }>();
onUserCreated.add(({ userId }) => sendWelcomeEmail(userId));
onUserCreated.add(({ userId }) => analytics.track("user_created", userId));
// New behavior → add new hook, no service change.
```
### Visitor pattern
```ts
interface NodeVisitor<R> {
visitNumber(n: NumberNode): R;
visitBinary(n: BinaryNode): R;
}
abstract class AstNode { abstract accept<R>(v: NodeVisitor<R>): R; }
class NumberNode extends AstNode {
constructor(public val: number) { super(); }
accept<R>(v: NodeVisitor<R>) { return v.visitNumber(this); }
}
class BinaryNode extends AstNode {
constructor(public op: "+"|"-", public l: AstNode, public r: AstNode) { super(); }
accept<R>(v: NodeVisitor<R>) { return v.visitBinary(this); }
}
// New traversal (printer, evaluator, optimizer) → new visitor class.
```
### Middleware chain
```ts
type Middleware<C> = (ctx: C, next: () => Promise<void>) => Promise<void>;
function compose<C>(mws: Middleware<C>[]): (ctx: C) => Promise<void> {
return async (ctx) => {
let i = -1;
const dispatch = async (idx: number): Promise<void> => {
if (idx <= i) throw new Error("next() called multiple times");
i = idx;
const fn = mws[idx];
if (!fn) return;
await fn(ctx, () => dispatch(idx + 1));
};
await dispatch(0);
};
}
const app = compose<{ req: Request; res?: Response }>([
async (c, next) => { console.log("log"); await next(); },
async (c, next) => { c.res = new Response("ok"); await next(); },
]);
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Multiple swappable algos | Strategy / interface |
| Closed set of variants | Discriminated union + exhaustive switch |
| Open set of behaviors | Plugin / hook registry |
| Tree traversal, multiple ops | Visitor |
| Pipeline | Middleware chain |
| Just one impl, no plans | Don't pre-abstract (YAGNI) |
**기본값**: 매 second variation 발견 시 abstract — 매 first time 의 over-abstraction X.
## 🔗 Graph
- 부모: [[SOLID-Principles]] · [[Design-Principles]]
- 변형: [[Visitor-Pattern]]
- 응용: [[Middleware]] · [[API 응답 및 상태 모델링 (State Modeling and API Responses)|Discriminated-Unions]] · [[Dependency_Injection_(DI)|Dependency-Injection]]
- Adjacent: [[DIP]] · [[Composition-over-Inheritance]]
## 🤖 LLM 활용
**언제**: 매 add-only domain (payment processor, plugin, parser AST), variant 매 자주 추가.
**언제 X**: 매 small / unchanging code — 매 abstraction 의 cost > benefit.
## ❌ 안티패턴
- **Premature abstraction**: 매 single impl 의 interface — 매 indirection 만 추가, value 0.
- **Speculative generality**: 매 "혹시 나중에" — YAGNI.
- **Inheritance for code reuse**: 매 LSP 위반 risk. Composition 우선.
- **Open everything**: 매 모든 method virtual / hook — 매 cognitive load 폭증.
- **Ignore exhaustiveness**: 매 switch default fallback — 매 new variant 의 silent skip.
## 🧪 검증 / 중복
- Verified (Meyer "Object-Oriented Software Construction", Martin "Clean Architecture", refactoring.guru).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — strategy, discriminated union, plugin/visitor/middleware 패턴 |
@@ -0,0 +1,229 @@
---
id: wiki-2026-0508-pinia
title: Pinia
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Vue Store, Pinia Store]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [vue, state-management, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Vue 3 / Pinia 2
---
# Pinia
## 매 한 줄
> **"매 Vue 3 매 official state management — 매 Vuex 4 후속, 매 Composition API native + 매 TypeScript-first"**. Eduardo San Martin Morote 매 2019 발표 매 Vue Core 팀 채택, 매 store 매 composable function 매 표현 매 boilerplate 90% 감소, 매 2026 매 Vue 표준 store library.
## 매 핵심
### 매 vs Vuex
- **No mutations**: 매 actions 가 directly state 수정 — 매 mutation indirection 제거.
- **Flat stores**: 매 nested module 매 X — 매 cross-store import 매 graph 형성.
- **Type inference**: 매 zero manual typing — 매 store usage 매 fully typed.
- **Devtools**: 매 timeline + state inspect + time-travel 지원.
### 매 store kinds
- **Options Store**: `state`, `getters`, `actions` — 매 Vuex-like familiar shape.
- **Setup Store**: `ref`/`computed`/`function` — 매 Composition API native.
### 매 응용
1. SPA global state (auth, user prefs).
2. Server-side rendering (Nuxt 3 매 native integration).
3. Cross-component caching (API result, derived state).
4. Plugin extension (persistedstate, undo/redo).
## 💻 패턴
### Setup store (modern preferred)
```ts
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() { count.value++ }
function reset() { count.value = 0 }
return { count, doubled, increment, reset }
})
```
### Options store
```ts
export const useUserStore = defineStore('user', {
state: () => ({
user: null as User | null,
loading: false,
}),
getters: {
isLoggedIn: (s) => s.user !== null,
displayName: (s) => s.user?.name ?? 'Guest',
},
actions: {
async login(creds: Credentials) {
this.loading = true
try {
this.user = await api.login(creds)
} finally {
this.loading = false
}
},
logout() { this.user = null },
},
})
```
### Component usage
```vue
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 매 reactivity 매 유지: storeToRefs 매 ref 변환
const { count, doubled } = storeToRefs(store)
// actions 매 destructure OK (not reactive)
const { increment } = store
</script>
<template>
<button @click="increment">{{ count }} (×2 = {{ doubled }})</button>
</template>
```
### App setup (Vue 3)
```ts
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
```
### Cross-store usage
```ts
import { useAuthStore } from './auth'
export const useCartStore = defineStore('cart', () => {
const auth = useAuthStore() // 매 다른 store 매 import + use
const items = ref<CartItem[]>([])
async function checkout() {
if (!auth.isLoggedIn) throw new Error('Login required')
return api.checkout(auth.user!.id, items.value)
}
return { items, checkout }
})
```
### $patch 매 batch update
```ts
const store = useUserStore()
store.$patch({ loading: false, user: newUser }) // 매 single devtools entry
// or function form for complex mutations
store.$patch((state) => {
state.cart.push(item)
state.lastUpdated = Date.now()
})
```
### $subscribe 매 store mutation 감지
```ts
store.$subscribe((mutation, state) => {
localStorage.setItem('cart', JSON.stringify(state))
}, { detached: true })
```
### Plugin (persistedstate)
```ts
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPersist)
// store 정의 시:
export const useUserStore = defineStore('user', {
state: () => ({ token: '' }),
persist: true, // localStorage 매 자동 sync
})
```
### SSR (Nuxt 3)
```ts
// composables/useAuth.ts
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
// 매 Nuxt 3 매 자동 hydration — server 매 set 한 state 매 client 매 transfer
return { user }
})
```
### Testing
```ts
import { setActivePinia, createPinia } from 'pinia'
import { beforeEach, expect, test } from 'vitest'
beforeEach(() => setActivePinia(createPinia()))
test('counter increments', () => {
const store = useCounterStore()
expect(store.count).toBe(0)
store.increment()
expect(store.count).toBe(1)
expect(store.doubled).toBe(2)
})
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Vue 3 new project | Pinia (default) |
| Vuex 4 migration | Pinia (incremental, alias module) |
| Composition API preference | Setup store |
| Vuex-familiar team | Options store |
| Component-local state | `ref`/`reactive` (no store) |
| Server state caching | TanStack Query / 매 store 의 X |
| SSR (Nuxt) | Pinia (built-in support) |
**기본값**: 매 setup store + TypeScript + storeToRefs.
## 🔗 Graph
- 부모: [[State-Management]]
- 변형: [[Vuex]] (predecessor) · [[프론트엔드_및_UIUX_표준|Redux]] · [[Zustand]] · [[Jotai]]
- 응용: [[Nuxt]]
- Adjacent: [[Composition-API]]
## 🤖 LLM 활용
**언제**: 매 Vue 3 global state, 매 cross-component sharing, 매 Nuxt 3 SSR state, 매 Vuex migration target.
**언제 X**: 매 component-local state (ref 만 충분), 매 server cache (TanStack Query 적합), 매 React project (Zustand/Redux).
## ❌ 안티패턴
- **storeToRefs 없이 destructure**: 매 reactivity loss — 매 `const { count } = store` 의 X.
- **Store action 매 component logic 침범**: 매 store 매 단순 state holder 매 됨 — 매 business logic 매 store 에 응집.
- **Nested store hierarchy 시도**: 매 flat 의 X — 매 cross-import 매 graph 형성.
- **Mutation pattern 의 반복**: 매 Vuex habit — 매 action 에서 직접 state 수정 OK.
- **매 component 매 store instance 다중 생성**: 매 useStore() 매 singleton — 매 매 호출 동일 instance.
## 🧪 검증 / 중복
- Verified (Pinia 2.x docs, Vue.js official).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — setup/options store + cross-store + SSR + plugin patterns |
@@ -0,0 +1,254 @@
---
id: wiki-2026-0508-principles
title: Principles
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Software Engineering Principles, Design Principles]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [design, principles, engineering, meta]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: meta
framework: cross-language
---
# Principles
## 매 한 줄
> **"매 software engineering 매 invariant guidelines — 매 decade-tested heuristics 매 specific tech 매 무관 적용"**. SOLID (Robert C. Martin 2000), DRY/KISS/YAGNI (XP 1999), Composition over Inheritance (GoF 1994) 매 backbone, 매 2026 매 LLM-augmented coding 매 시대 매 여전히 readability/maintainability 매 governing axis.
## 매 핵심
### 매 SOLID
- **S — Single Responsibility**: 매 module 매 매 reason-to-change 매 하나.
- **O — Open/Closed**: 매 extension 매 open, 매 modification 매 closed.
- **L — Liskov Substitution**: 매 subtype 매 base type 매 자리 매 대체 가능.
- **I — Interface Segregation**: 매 client 매 unused method 매 의존 X.
- **D — Dependency Inversion**: 매 high-level module 매 low-level 매 의존 X — 매 abstraction 매 의존.
### 매 핵심 short heuristics
- **DRY (Don't Repeat Yourself)**: 매 knowledge 매 single representation.
- **KISS (Keep It Simple, Stupid)**: 매 simplest thing 매 works.
- **YAGNI (You Aren't Gonna Need It)**: 매 future-need 매 가정 X.
- **Composition over Inheritance**: 매 has-a 매 is-a 보다 flexible.
- **Tell, Don't Ask**: 매 object 매 데이터 묻지 말고 매 행동 요청.
- **Law of Demeter**: 매 friend-of-friend 매 의존 X (`a.b.c.d()` 매 X).
- **Principle of Least Astonishment**: 매 행동 매 사용자 expectation 매 일치.
- **Fail Fast**: 매 오류 매 early surface — 매 silent corruption 매 X.
- **Boy Scout Rule**: 매 떠날 때 매 코드 매 더 깨끗하게.
### 매 응용
1. Code review checklist (SOLID violations, DRY 적용 검토).
2. Architecture decision record (principle 매 trade-off rationale).
3. Onboarding (junior dev 매 mental model).
4. LLM prompt engineering (gen 시 매 principle 매 명시 → quality ↑).
## 💻 패턴
### SRP — split reasons-to-change
```ts
// 매 BAD — 매 두 reasons (HTTP + persistence)
class UserController {
async register(req, res) {
const user = req.body
if (!user.email) return res.status(400).send('email required')
await db.query('INSERT INTO users ...', [user])
return res.send('ok')
}
}
// 매 GOOD
class UserController {
constructor(private validator: UserValidator, private repo: UserRepo) {}
async register(req, res) {
const result = this.validator.validate(req.body)
if (!result.ok) return res.status(400).send(result.error)
await this.repo.save(req.body)
return res.send('ok')
}
}
```
### OCP — strategy pattern
```ts
// 매 BAD — 매 새 type 매 추가 시 매 modify
function calc(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.r ** 2
case 'square': return shape.side ** 2
// 매 새 type → 매 여기 수정 필요
}
}
// 매 GOOD — 매 extension 매 open
interface Shape { area(): number }
class Circle implements Shape { constructor(public r: number) {}; area() { return Math.PI * this.r ** 2 } }
class Square implements Shape { constructor(public side: number) {}; area() { return this.side ** 2 } }
class Triangle implements Shape { /* 매 새 file — 매 기존 코드 unchanged */ }
```
### LSP — substitutability
```ts
// 매 BAD — Square 매 Rectangle 의 LSP 위반
class Rectangle {
constructor(public w: number, public h: number) {}
setW(w: number) { this.w = w }
setH(h: number) { this.h = h }
}
class Square extends Rectangle {
setW(w: number) { this.w = w; this.h = w } // 매 surprise
setH(h: number) { this.w = h; this.h = h }
}
function expand(r: Rectangle) {
r.setW(5); r.setH(10)
console.assert(r.w === 5 && r.h === 10) // Square 매 fail
}
// 매 GOOD — 매 hierarchy 분리 / composition
```
### ISP — fat interface 매 분할
```ts
// 매 BAD
interface Worker {
work(): void
eat(): void
sleep(): void
}
class Robot implements Worker {
work() {}
eat() { throw new Error('robots dont eat') } // 매 ISP 위반
sleep() { throw new Error('robots dont sleep') }
}
// 매 GOOD
interface Workable { work(): void }
interface Eatable { eat(): void }
interface Sleepable { sleep(): void }
class Robot implements Workable { work() {} }
class Human implements Workable, Eatable, Sleepable { /* */ }
```
### DIP — depend on abstractions
```ts
// 매 BAD — 매 high-level (OrderService) 매 low-level (MysqlOrderRepo) 매 의존
class OrderService {
private repo = new MysqlOrderRepo()
place(order: Order) { this.repo.save(order) }
}
// 매 GOOD
interface OrderRepo { save(o: Order): Promise<void> }
class OrderService {
constructor(private repo: OrderRepo) {}
place(o: Order) { return this.repo.save(o) }
}
class MysqlOrderRepo implements OrderRepo { /* */ }
class PostgresOrderRepo implements OrderRepo { /* */ }
```
### Composition over Inheritance
```ts
// 매 BAD
class Duck {
fly() { /* */ }
quack() { /* */ }
}
class RubberDuck extends Duck {
fly() { throw new Error("can't fly") } // 매 LSP 위반
}
// 매 GOOD — 매 strategy composition
interface FlyBehavior { fly(): void }
interface QuackBehavior { quack(): void }
class Duck {
constructor(private flyB: FlyBehavior, private quackB: QuackBehavior) {}
performFly() { this.flyB.fly() }
performQuack() { this.quackB.quack() }
}
const rubberDuck = new Duck(new FlyNoWay(), new Squeak())
const mallard = new Duck(new FlyWithWings(), new NormalQuack())
```
### Tell Don't Ask
```ts
// 매 BAD — 매 ask
if (account.balance > amount) {
account.balance -= amount
ledger.record(amount)
}
// 매 GOOD — 매 tell
account.withdraw(amount, ledger) // 매 객체 매 캡슐화 + 매 invariant 매 보장
```
### Law of Demeter
```ts
// 매 BAD — 매 train wreck
const street = order.customer.address.street
// 매 GOOD — 매 method 매 노출
const street = order.customerStreet() // 매 Order 매 delegate
```
### Fail Fast
```ts
// 매 BAD — 매 silent fallback
function parse(s: string) {
try { return JSON.parse(s) } catch { return {} } // 매 caller 매 깨진 data 매 모름
}
// 매 GOOD — 매 explicit
function parse(s: string): Result<unknown, Error> {
try { return { ok: true, value: JSON.parse(s) } }
catch (e) { return { ok: false, error: e as Error } }
}
```
## 매 결정 기준
| 상황 | Principle |
|---|---|
| God class refactor | SRP — split |
| 매 새 변형 매 추가 매 잦은 수정 | OCP — strategy |
| Subtype 매 caller surprise | LSP — rethink hierarchy |
| Interface 매 unused methods | ISP — split |
| Hard-coded dependency | DIP — inject |
| Repeated code 3+ places | DRY — extract |
| Speculative generality | YAGNI — delete |
| Surprise behavior | Least Astonishment — rename/redesign |
| Hidden errors | Fail Fast — throw early |
**기본값**: 매 SOLID + 매 KISS + 매 YAGNI — 매 over-abstraction 매 함정 매 회피.
## 🔗 Graph
- 변형: [[SOLID]] · [[Clean-Architecture]] · [[Hexagonal-Architecture]]
- 응용: [[Code-Review]] · [[Refactoring_Best_Practices|Refactoring]] · [[Architecture-Decision-Record]]
- Adjacent: [[Design-Patterns]] · [[Domain-Driven-Design]] · [[Test-Driven-Development]]
## 🤖 LLM 활용
**언제**: 매 design review, 매 refactor planning, 매 principle violation 매 식별, 매 mentoring/teaching context.
**언제 X**: 매 throwaway script (overhead), 매 dogmatic application — 매 principle 매 means 매 X end.
## ❌ 안티패턴
- **매 principle 매 dogma**: 매 SRP 매 따라 매 1-line class 매 폭발 — 매 trade-off.
- **DRY over-application**: 매 coincidental duplication 매 동일 함수 묶음 — 매 false abstraction.
- **YAGNI 매 결여**: 매 "future-proof" 매 매 무한 layer.
- **DIP 매 always**: 매 simple CRUD 매 abstract repo — 매 over-engineer.
- **OCP 매 rule**: 매 모든 enum 매 polymorphism 변환 — 매 readability ↓.
- **Pattern dropping**: 매 Strategy/Factory/Observer 매 nameset — 매 problem 매 first.
## 🧪 검증 / 중복
- Verified (Clean Code/Architecture by R.C. Martin, Design Patterns GoF, Pragmatic Programmer).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — SOLID + heuristics + violation/fix patterns matrix |
@@ -0,0 +1,165 @@
---
id: wiki-2026-0508-probabilistic-graphical-models
title: Probabilistic Graphical Models
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [PGM, Bayesian Network, Markov Random Field, MRF]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [pgm, bayesian-network, mrf, inference, machine-learning]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: Python
framework: pgmpy/pyro/numpyro
---
# Probabilistic Graphical Models
## 매 한 줄
> **"매 graph 로 joint distribution 의 factorization 표현"**. 매 random variable = node, dependency = edge. Bayesian Network (DAG) 와 Markov Random Field (undirected) 의 두 family. 2026 의 매 deep learning 시대에도 medical diagnosis, fault detection, causal inference 에서 핵심.
## 매 핵심
### 매 Two Families
- **Bayesian Network (Directed)**: P(X) = ∏ᵢ P(Xᵢ | Pa(Xᵢ)). 매 causal direction 명시.
- **Markov Random Field (Undirected)**: P(X) = (1/Z) ∏ φc(Xc). 매 symmetric correlation.
- **Factor Graph**: 매 두 family 통합 representation — bipartite (variable + factor nodes).
### 매 핵심 Operation
- **Marginal inference**: P(Xᵢ) 매 sum out 다른 variable.
- **MAP inference**: argmax P(X) — most likely assignment.
- **Conditional**: P(Xᵢ | E=e) — evidence 주어진 belief update.
- **Learning**: 매 parameter (MLE, EM) + structure (score-based, constraint-based).
### 매 Inference Algorithm
- **Exact**: Variable Elimination, Belief Propagation (tree), Junction Tree.
- **Approx**: MCMC (Gibbs, Metropolis-Hastings), Variational Inference, Loopy BP.
### 매 응용
1. Medical diagnosis: 매 symptom → disease causal network.
2. Fault detection: 매 sensor reading → root cause.
3. Computer vision: 매 CRF for image segmentation (DeepLab v3 의 backbone).
4. Causal inference: 매 do-calculus, counterfactual.
## 💻 패턴
### pgmpy: Bayesian Network 정의
```python
from pgmpy.models import DiscreteBayesianNetwork
from pgmpy.factors.discrete import TabularCPD
model = DiscreteBayesianNetwork([('Rain', 'Sprinkler'), ('Rain', 'Wet'), ('Sprinkler', 'Wet')])
cpd_rain = TabularCPD('Rain', 2, [[0.8], [0.2]])
cpd_sprinkler = TabularCPD('Sprinkler', 2, [[0.9, 0.5], [0.1, 0.5]], evidence=['Rain'], evidence_card=[2])
cpd_wet = TabularCPD('Wet', 2,
[[1.0, 0.2, 0.1, 0.01], [0.0, 0.8, 0.9, 0.99]],
evidence=['Rain', 'Sprinkler'], evidence_card=[2, 2])
model.add_cpds(cpd_rain, cpd_sprinkler, cpd_wet)
assert model.check_model()
```
### Variable Elimination inference
```python
from pgmpy.inference import VariableElimination
infer = VariableElimination(model)
result = infer.query(variables=['Rain'], evidence={'Wet': 1})
print(result)
```
### NumPyro: Bayesian regression
```python
import numpyro
import numpyro.distributions as dist
from numpyro.infer import MCMC, NUTS
import jax.numpy as jnp
def model(X, y=None):
beta = numpyro.sample('beta', dist.Normal(jnp.zeros(X.shape[1]), 1.0))
sigma = numpyro.sample('sigma', dist.HalfNormal(1.0))
mu = jnp.dot(X, beta)
numpyro.sample('y', dist.Normal(mu, sigma), obs=y)
mcmc = MCMC(NUTS(model), num_warmup=500, num_samples=1000)
mcmc.run(jax.random.PRNGKey(0), X, y)
```
### Markov Random Field (CRF for sequence labeling)
```python
import torch
from torchcrf import CRF
num_tags = 5
crf = CRF(num_tags, batch_first=True)
emissions = torch.randn(2, 10, num_tags) # (batch, seq, tags)
tags = torch.randint(0, num_tags, (2, 10))
loss = -crf(emissions, tags) # neg log likelihood
best = crf.decode(emissions) # Viterbi
```
### Pyro: Variational Inference
```python
import pyro
import pyro.distributions as dist
from pyro.infer import SVI, Trace_ELBO
from pyro.optim import Adam
def model(data):
z = pyro.sample('z', dist.Normal(0, 1))
pyro.sample('obs', dist.Normal(z, 1), obs=data)
def guide(data):
mu = pyro.param('mu', torch.tensor(0.))
sigma = pyro.param('sigma', torch.tensor(1.), constraint=dist.constraints.positive)
pyro.sample('z', dist.Normal(mu, sigma))
svi = SVI(model, guide, Adam({'lr': 0.01}), Trace_ELBO())
for step in range(1000):
svi.step(data)
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Discrete, small state space, exact inference | pgmpy + VE |
| Continuous, large model | NumPyro + NUTS |
| Sequence labeling | CRF |
| Image segmentation | CRF + CNN (DeepLab) |
| Causal inference | DoWhy + pgmpy |
| Real-time, approx OK | Loopy BP / VI |
**기본값**: 매 small problem → pgmpy. 매 large continuous → NumPyro NUTS.
## 🔗 Graph
- 부모: [[Probability_Theory]] · [[Graph_Theory]]
- 변형: [[Bayesian_Network]] · [[Markov_Random_Field]]
- 응용: [[Image_Segmentation]] · [[Causal_Inference]]
- Adjacent: [[Hidden_Markov_Model]] · [[Variational_Inference]] · [[MCMC]]
## 🤖 LLM 활용
**언제**: 매 explicit causal structure 필요, interpretability 중요, small-data domain (medicine).
**언제 X**: 매 large-scale perception (이미지/음성) — neural network 가 우수.
## ❌ 안티패턴
- **모든 변수 fully connected**: 매 parameter explosion — sparsity 활용.
- **Exact inference 의 강행 in dense graph**: NP-hard — approximate 사용.
- **Causal direction 의 임의 가정**: 매 domain knowledge 없으면 PC algorithm 등으로 학습.
## 🧪 검증 / 중복
- Verified (Koller & Friedman 2009, Murphy 2012, pgmpy 0.1.26, NumPyro 0.16).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — pgmpy/NumPyro/Pyro modern stack |
@@ -0,0 +1,204 @@
---
id: wiki-2026-0508-react-native-web-desktop
title: React Native Web — Desktop
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [React Native Web Desktop, RNW Desktop, Universal RN]
duplicate_of: none
source_trust_level: A
confidence_score: 0.85
verification_status: applied
tags: [react-native, react-native-web, desktop, cross-platform, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React Native Web
---
# React Native Web — Desktop
## 매 한 줄
> **"매 한 codebase 의 mobile + desktop"**. React Native Web (RNW) 매 RN component 를 DOM 으로 render — Expo Web 또는 standalone Vite/Next.js 셋업으로 매 desktop browser target. 2026 기준 Expo Router v4 + RN 0.76 New Architecture 매 stable, Bridgeless 매 web 의 hot reload 빠름. Tamagui / NativeWind 매 styling 의 cross-platform.
## 매 핵심
### 매 왜 desktop 의 RNW
- 매 mobile-first 의 enterprise dashboard / electron 대체.
- 매 single team / single component library.
- Server-side render (Next.js + RNW 0.19+) 매 SEO 가능.
- Tablet / iPad split-view 의 fluid layout.
### 매 vs alternatives
- **Electron**: 매 heavy bundle, native shell, 매 different mental model.
- **Tauri**: 매 Rust + WebView — 매 React but 매 RN component X.
- **PWA + responsive**: 매 web-first, mobile compromise.
- **RNW**: 매 mobile-first, desktop adapt.
### 매 응용
1. Expo Router universal app — iOS + Android + Web (single team).
2. Internal tool / admin — RN component library 재사용.
3. Linear/Notion-style desktop app — RNW + Tauri shell.
4. Marketing site + app — same brand component.
## 💻 패턴
### Expo Router universal entry
```tsx
// app/_layout.tsx
import { Stack } from "expo-router";
import { useDeviceOrientation } from "@react-native-community/hooks";
export default function Layout() {
return (
<Stack
screenOptions={{
headerShown: true,
contentStyle: { maxWidth: 1280, alignSelf: "center", width: "100%" },
}}
/>
);
}
```
### Platform-specific file (`.web.tsx`)
```tsx
// FilePicker.tsx (mobile)
// FilePicker.web.tsx (desktop)
// FilePicker.web.tsx
import { useRef } from "react";
import { Pressable, Text } from "react-native";
export function FilePicker({ onPick }: { onPick: (f: File) => void }) {
const inputRef = useRef<HTMLInputElement>(null);
return (
<>
<Pressable onPress={() => inputRef.current?.click()}>
<Text>Pick file</Text>
</Pressable>
<input
ref={inputRef}
type="file"
style={{ display: "none" }}
onChange={(e) => e.target.files?.[0] && onPick(e.target.files[0])}
/>
</>
);
}
```
### Responsive layout (useWindowDimensions)
```tsx
import { View, useWindowDimensions } from "react-native";
export function Layout({ children }: { children: React.ReactNode }) {
const { width } = useWindowDimensions();
const isDesktop = width >= 1024;
return (
<View style={{ flexDirection: isDesktop ? "row" : "column", flex: 1 }}>
{isDesktop && <Sidebar />}
<View style={{ flex: 1 }}>{children}</View>
</View>
);
}
```
### Keyboard shortcuts (web only)
```tsx
import { useEffect } from "react";
import { Platform } from "react-native";
export function useShortcut(key: string, handler: () => void) {
useEffect(() => {
if (Platform.OS !== "web") return;
const fn = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === key) {
e.preventDefault();
handler();
}
};
window.addEventListener("keydown", fn);
return () => window.removeEventListener("keydown", fn);
}, [key, handler]);
}
```
### Hover state (`Pressable` web-only)
```tsx
import { Pressable, Text } from "react-native";
<Pressable
style={({ hovered, pressed }: any) => ({
backgroundColor: pressed ? "#e0e0e0" : hovered ? "#f0f0f0" : "white",
padding: 12,
borderRadius: 8,
})}
>
{() => <Text>Hover me (web)</Text>}
</Pressable>
```
### Tamagui styling (cross-platform)
```tsx
import { Button, Stack, Text } from "tamagui";
export function Card() {
return (
<Stack padding="$4" borderRadius="$4" backgroundColor="$background" hoverStyle={{ backgroundColor: "$backgroundHover" }}>
<Text fontSize="$6">Title</Text>
<Button theme="active">Action</Button>
</Stack>
);
}
```
### Next.js + RNW (SSR setup)
```js
// next.config.js
const { withExpo } = require("@expo/next-adapter");
module.exports = withExpo({
transpilePackages: ["react-native", "react-native-web", "expo"],
experimental: { forceSwcTransforms: true },
});
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Mobile + desktop, same product | Expo Router universal |
| Native desktop window/menu | Electron / Tauri (RNW inside Tauri OK) |
| Heavy animations | Reanimated 4 (web target supported) |
| Styling | Tamagui / NativeWind |
| SSR/SEO | Next.js + RNW |
| Mobile only | plain RN (no RNW) |
**기본값**: Expo Router + Tamagui — 매 mobile-first, desktop 매 responsive breakpoint.
## 🔗 Graph
- 부모: [[React-Native]]
- 변형: [[Expo-Router]]
- 응용: [[Universal-App]]
- Adjacent: [[Tauri]] · [[Electron]]
## 🤖 LLM 활용
**언제**: mobile-first product 의 desktop site, internal tool with RN library, universal team.
**언제 X**: desktop-only 의 native window/menu/system tray — 매 Electron/Tauri.
## ❌ 안티패턴
- **`document.getElementById` 직접 사용**: 매 mobile crash. Platform.OS gate 필수.
- **Mobile-only 라이브러리 import**: 매 web bundle 의 fail. `.web.tsx` split.
- **Fixed pixel layout**: 매 desktop wide screen 매 broken. Flex / max-width.
- **No keyboard navigation**: 매 desktop a11y 폭망.
## 🧪 검증 / 중복
- Verified (Expo SDK 51+ docs, react-native-web 0.19+ docs, Tamagui docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Expo Router, .web.tsx, hover/shortcut, Tamagui, Next adapter 패턴 |
@@ -0,0 +1,174 @@
---
id: wiki-2026-0508-readonly-type
title: Readonly Type
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [TypeScript Readonly, ReadonlyArray, Readonly Utility Type]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, readonly, immutability, type-system]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: TS 5.7+
---
# Readonly Type
## 매 한 줄
> **"매 TypeScript 의 compile-time immutability"**. 매 `readonly` modifier (property level) + `Readonly<T>` utility type (object) + `ReadonlyArray<T>` (array) + `as const` (literal). 매 runtime 강제 X — 매 dev-time discipline. 2026 TS 5.7+ 매 `satisfies` / `const` type parameter 와 조합으로 강력.
## 매 핵심
### 매 4가지 형태
- **`readonly` property**: `interface User { readonly id: number; }` — 단일 property.
- **`Readonly<T>`**: 매 mapped type, 매 모든 property readonly.
- **`ReadonlyArray<T>` / `readonly T[]`**: 매 array, push/pop 등 mutation method 제거.
- **`as const`**: 매 literal value, deeply readonly + literal type 보존.
### 매 한계
- **Compile-time only**: 매 runtime mutation 가능 (cast, JS interop).
- **Shallow**: 매 `Readonly<T>` 매 nested object 는 mutable.
- **Type erasure**: 매 컴파일 후 일반 JS object — `Object.freeze()` 와 무관.
### 매 응용
1. Function parameter: 매 mutation 의 의도 차단.
2. State: 매 Redux store, React state — 매 immutable update 강제.
3. Configuration: 매 `as const` 로 literal type 보존.
4. API response type: 매 response 의 mutation 차단.
## 💻 패턴
### `readonly` property
```ts
interface User {
readonly id: number;
name: string;
}
const u: User = { id: 1, name: 'Alice' };
u.name = 'Bob'; // OK
u.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
```
### `Readonly<T>` utility
```ts
interface Config {
apiUrl: string;
timeout: number;
}
const config: Readonly<Config> = { apiUrl: '/api', timeout: 5000 };
config.apiUrl = '/v2'; // Error
```
### `ReadonlyArray<T>` / `readonly T[]`
```ts
function sum(nums: readonly number[]): number {
// nums.push(0); // Error
return nums.reduce((a, b) => a + b, 0);
}
const arr: readonly number[] = [1, 2, 3];
arr[0] = 99; // Error
```
### `as const` (deeply readonly literal)
```ts
const ROUTES = {
HOME: '/',
USERS: '/users',
POSTS: '/posts',
} as const;
type Route = typeof ROUTES[keyof typeof ROUTES]; // '/' | '/users' | '/posts'
const TUPLE = [1, 'two', true] as const;
// type: readonly [1, 'two', true] (not (number | string | boolean)[])
```
### Deep Readonly (recursive)
```ts
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface State {
user: { name: string; settings: { theme: string } };
}
const s: DeepReadonly<State> = { user: { name: 'A', settings: { theme: 'dark' } } };
s.user.settings.theme = 'light'; // Error
```
### `satisfies` + `as const` (TS 5.7)
```ts
const config = {
port: 3000,
host: 'localhost',
} as const satisfies { port: number; host: string };
// config.port type: 3000 (literal), still validated
```
### Function `const` type parameter (TS 5.0+)
```ts
function tuple<const T extends readonly unknown[]>(arr: T): T {
return arr;
}
const t = tuple(['a', 'b', 'c']); // type: readonly ['a', 'b', 'c']
```
### Branded readonly (runtime + type)
```ts
function freeze<T>(obj: T): Readonly<T> {
return Object.freeze(obj) as Readonly<T>;
}
const config = freeze({ apiUrl: '/api' });
// config.apiUrl = '/v2'; // TS error + runtime error
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Single property immutable | `readonly` modifier |
| Whole object immutable (shallow) | `Readonly<T>` |
| Array param (no mutation) | `readonly T[]` |
| Literal config | `as const` |
| Deeply nested state | `DeepReadonly<T>` 또는 Immer |
| Runtime guarantee 필요 | `Object.freeze()` + `Readonly<T>` |
| React state | useState (immutable update convention) |
**기본값**: 매 function parameter 매 `readonly T[]` 항상. 매 literal config 매 `as const`. 매 deep immutability 매 Immer.
## 🔗 Graph
- 부모: [[TypeScript]]
- 변형: [[const_assertion]]
- 응용: [[프론트엔드_및_UIUX_표준|Redux]]
- Adjacent: [[satisfies_operator]]
## 🤖 LLM 활용
**언제**: API response type, function param (mutation 의도 차단), config object (`as const`), Redux state.
**언제 X**: 매 internal mutation-heavy class — readonly 의 noise.
## ❌ 안티패턴
- **`Readonly<T>` 의 deep 가정**: shallow 만 — `DeepReadonly<T>` 또는 Immer 사용.
- **Runtime immutability 가정**: TS readonly 매 compile-time only — JS cast 시 mutable.
- **`Readonly<T>` + class with method**: method 의 type 까지 readonly 처리 — 매 의도와 다른 동작 가능.
- **`as const` 남용 in mutable context**: 매 literal type 의 narrowness 가 unintended widening 차단.
## 🧪 검증 / 중복
- Verified (TypeScript 5.7 docs, TS handbook).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — `as const` / `satisfies` / `const` type param 추가 |
@@ -0,0 +1,191 @@
---
id: wiki-2026-0508-rollup
title: Rollup
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [rollup-bundler, rollupjs]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [bundler, build-tool, esm, library-build, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: javascript
framework: rollup-4
---
# Rollup
## 매 한 줄
> **"매 ESM-first 의 module bundler 의 library-grade output 의 specialized."**. Rich Harris 의 2015 의 의 originated, 매 tree-shaking 의 pioneer — 매 2026 의 Rollup 4.x 의 매 SWC/Oxc-powered, library 의 publishing (NPM packages, SDKs) 의 의 default choice 이며, Vite 의 production build 의 underlying engine.
## 매 핵심
### 매 정의
- ESM-native bundler — `import`/`export` 의 first-class.
- Tree-shaking 의 pioneer (2015) — dead code elimination via static analysis.
- Output formats: ESM, CJS, UMD, IIFE, AMD, SystemJS.
- Plugin-driven — minimal core, everything via `@rollup/plugin-*`.
### 매 vs. 다른 bundlers
- **Vite (dev)**: Rollup 의 production 의 wraps — dev 의 esbuild + Rollup of build.
- **webpack**: 매 application bundling 의 strong, code splitting 의 mature; Rollup 의 library output 의 cleaner.
- **esbuild**: 10-100x faster, 그러나 plugin ecosystem 의 narrower.
- **Bun.build / tsdown**: 2026 의 emerging, Rollup-compatible plugin API.
### 매 응용
1. NPM library publishing (React component lib, SDK, utils package).
2. Multi-format output (ESM + CJS + types).
3. Vite 의 production build (transitively).
4. Storybook 의 build pipeline.
## 💻 패턴
### Library bundle (TS + multi-format)
```js
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import dts from 'rollup-plugin-dts';
export default [
{
input: 'src/index.ts',
output: [
{ file: 'dist/index.cjs', format: 'cjs', sourcemap: true },
{ file: 'dist/index.mjs', format: 'esm', sourcemap: true },
],
plugins: [nodeResolve(), commonjs(), typescript({ tsconfig: './tsconfig.build.json' })],
external: ['react', 'react-dom'],
},
{
input: 'src/index.ts',
output: [{ file: 'dist/index.d.ts', format: 'es' }],
plugins: [dts()],
},
];
```
### `package.json` exports map
```json
{
"name": "@org/lib",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"files": ["dist"]
}
```
### Tree-shaking 의 verify
```js
// rollup.config.js
export default {
// ...
treeshake: {
moduleSideEffects: false, // aggressive — assume no side effects
propertyReadSideEffects: false,
},
};
```
### Plugin: image inlining
```js
import image from '@rollup/plugin-image';
import url from '@rollup/plugin-url';
export default {
plugins: [
url({ limit: 8192 }), // inline <8kb as base64
image(),
],
};
```
### React component library
```js
import { babel } from '@rollup/plugin-babel';
import postcss from 'rollup-plugin-postcss';
export default {
input: 'src/index.tsx',
output: { dir: 'dist', format: 'esm', preserveModules: true },
external: ['react', 'react-dom', /^@radix-ui/],
plugins: [
postcss({ modules: true, extract: 'styles.css' }),
babel({ babelHelpers: 'bundled', presets: ['@babel/preset-react'] }),
],
};
```
### Watch mode + dev server
```bash
rollup -c -w
# or use Vite which wraps Rollup for production
vite build
```
### Code splitting (manual chunks)
```js
export default {
input: { main: 'src/main.ts', admin: 'src/admin.ts' },
output: {
dir: 'dist',
format: 'esm',
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash-es', 'date-fns'],
},
},
};
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Library / SDK publishing | **Rollup** (clean output, multi-format) |
| Application bundle | **Vite** (Rollup 의 wraps) or webpack |
| Speed-critical (CI) | **esbuild** or **tsdown** (Rolldown) |
| Storybook / docs build | Rollup-based (Vite mode) |
| Monorepo internal package | tsup (esbuild) or Rollup with `preserveModules` |
**기본값**: library 의 의 Rollup, app 의 의 Vite (Rollup 의 production engine).
## 🔗 Graph
- 변형: [[Vite]] · [[tsup]]
- 응용: [[Component-Library-Architecture]]
- Adjacent: [[ESM]] · [[esbuild]]
## 🤖 LLM 활용
**언제**: NPM library publishing, multi-format output, clean ESM bundles, Vite production tuning.
**언제 X**: large application bundling (Vite/webpack), HMR-heavy dev (Vite), Node.js server (no bundling needed in 2026 ESM).
## ❌ 안티패턴
- **Bundling peer deps**: `react`/`react-dom` 의 bundle 의 included — `external` 의 declare 의 필요.
- **CJS-only output 의 ESM-only consumer**: 2026 의 ESM-first ecosystem — dual ESM+CJS output 의 ship.
- **Source map 의 omission**: production debugging 의 impossible — `sourcemap: true` 의 default.
- **Plugin order 의 ignorance**: `nodeResolve` before `commonjs` before `typescript` — order matters.
- **Vite app 의 직접 의 Rollup config**: Vite 의 abstracts — `vite.config.ts``build.rollupOptions` 의 사용.
## 🧪 검증 / 중복
- Verified (rollupjs.org, Rollup 4.x docs 2026, Vite documentation).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full bundler reference + 2026 ecosystem (Rolldown, tsup) |
@@ -0,0 +1,203 @@
---
id: wiki-2026-0508-scss-sass
title: SCSS (Sass)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Sass, SCSS, Dart Sass]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, css, scss, sass, preprocessor]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: SCSS
framework: Dart Sass
---
# SCSS (Sass)
## 매 한 줄
> **"매 CSS 의 superset preprocessor — variable, nesting, mixin, module"**. 2006 Hampton Catlin 출시. 2020 Dart Sass 의 only 공식 implementation (LibSass deprecated). 2026 매 Tailwind v4 / native CSS nesting 의 부상으로 점유율 감소했지만 매 design system / legacy 에서 견고.
## 매 핵심
### 매 두 syntax
- **SCSS**: 매 CSS-superset, `{}` `;` 사용 — 매 mainstream.
- **Sass (indented)**: 매 Python-like indentation, `.sass` 확장자 — 매 niche.
### 매 핵심 기능
- Variable: `$primary: #007bff;`
- Nesting: 매 selector 중첩 + `&` (parent reference).
- Mixin: `@mixin` / `@include` — reusable block.
- Function: `@function` — value 반환.
- Module system: `@use` / `@forward` (2019+, 매 `@import` deprecated).
- Inheritance: `@extend`.
- Math/color operations.
### 매 2026 상태
- Native CSS: nesting (Baseline 2024), `var()`, `@layer`, container query — 매 SCSS 의 일부 기능 native.
- Tailwind v4: 매 utility-first → SCSS 의 design system 사용 감소.
- 여전히 활발: Bootstrap 5, Material Design legacy, Rails 8 default.
### 매 응용
1. Design system: 매 token (color, spacing, typography) 의 SCSS variable.
2. Component library: 매 mixin 으로 button/card variant 생성.
3. Theme switching: 매 SCSS map + CSS custom property hybrid.
## 💻 패턴
### Variable + Nesting
```scss
$primary: #007bff;
$radius: 4px;
.card {
background: white;
border-radius: $radius;
&__header {
color: $primary;
&:hover {
color: darken($primary, 10%);
}
}
}
```
### Mixin
```scss
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
justify-content: center;
align-items: center;
}
.modal {
@include flex-center(column);
}
```
### `@use` module system (modern, replaces `@import`)
```scss
// _colors.scss
$primary: #007bff;
$secondary: #6c757d;
// _mixins.scss
@mixin shadow($level: 1) {
box-shadow: 0 #{$level * 2}px #{$level * 4}px rgba(0,0,0,0.1);
}
// app.scss
@use 'colors' as c;
@use 'mixins' as m;
.button {
background: c.$primary;
@include m.shadow(2);
}
```
### Map + each loop (theme tokens)
```scss
$spacing: (
xs: 4px,
sm: 8px,
md: 16px,
lg: 24px,
);
@each $name, $value in $spacing {
.p-#{$name} { padding: $value; }
.m-#{$name} { margin: $value; }
}
```
### CSS custom property + SCSS hybrid (theme switch)
```scss
:root {
--bg: #fff;
--text: #000;
}
[data-theme='dark'] {
--bg: #111;
--text: #eee;
}
@function token($name) {
@return var(--#{$name});
}
body {
background: token(bg);
color: token(text);
}
```
### Function
```scss
@function rem($px) {
@return #{$px / 16}rem;
}
.title {
font-size: rem(24); // 1.5rem
}
```
### Vite 7 SCSS 설정
```ts
// vite.config.ts
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler', // Dart Sass embedded API
additionalData: `@use "@/styles/_globals" as *;`,
},
},
},
});
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| New project (greenfield 2026) | Native CSS + Tailwind v4 |
| Existing Bootstrap project | SCSS (built-in) |
| Design system with tokens | SCSS map + CSS var hybrid |
| Theme switching | CSS custom property (SCSS optional) |
| Rails 8 / Phoenix | SCSS (default) |
| React app | CSS Modules / Tailwind / vanilla-extract |
**기본값**: 매 새 project 매 native CSS + Tailwind v4. 매 SCSS 매 design-system / legacy 의 limited use.
## 🔗 Graph
- 응용: [[Material_Design]] · [[Design_System]]
- Adjacent: [[Tailwind_CSS]] · [[CSS_Modules]] · [[vanilla-extract]]
## 🤖 LLM 활용
**언제**: 매 large design system, theme variable, legacy Bootstrap project.
**언제 X**: 매 utility-first (Tailwind), 매 native CSS nesting/var 으로 충분한 경우.
## ❌ 안티패턴
- **`@import` 사용**: deprecated (2024) — 매 `@use`/`@forward` 사용.
- **Deep nesting (4+ level)**: 매 specificity 폭발, BEM-like flat 으로.
- **LibSass 사용**: 매 deprecated 2020 — Dart Sass 만.
- **Color function 의 변경 무시**: 매 `darken()` 의 공식 deprecation (2024) → `color.adjust()` 사용.
## 🧪 검증 / 중복
- Verified (Sass 1.81+, Dart Sass docs, Vite 7 release).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — `@use` module / Dart Sass / Vite 통합 |
@@ -0,0 +1,227 @@
---
id: wiki-2026-0508-seo-중심의-마케팅-및-블로그-사이트-구축
title: SEO 중심의 마케팅 및 블로그 사이트 구축
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [seo-marketing-site, blog-site-construction]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [seo, marketing-site, blog, ssg, frontend, next-js, astro]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: astro-next-js
---
# SEO 중심의 마케팅 및 블로그 사이트 구축
## 매 한 줄
> **"매 organic search 의 의 LCP < 2.5s + crawlability + structured data + content velocity 의 four-pillar 의 site architecture."**. 2026 의 의 SEO-first build 의 의 default stack 의 Astro / Next.js (SSG) + MDX content + Schema.org JSON-LD + Core Web Vitals optimization — 매 GEO (Generative Engine Optimization, AI overview) 의 등장 의 의 traditional SEO 의 augment.
## 매 핵심
### 매 4-pillar
1. **Crawlability**: server-rendered HTML, sitemap, robots.txt, canonical URLs, hreflang.
2. **Performance**: LCP < 2.5s, INP < 200ms, CLS < 0.1 — Core Web Vitals 의 ranking factor.
3. **Content**: depth, freshness, E-E-A-T (Experience, Expertise, Authoritativeness, Trust).
4. **Structure**: Schema.org JSON-LD, OpenGraph, semantic HTML, internal linking.
### 매 stack 선택
- **Astro**: content-heavy, Islands architecture, minimal JS — best LCP.
- **Next.js (App Router, SSG mode)**: hybrid 가능, MDX support, vast ecosystem.
- **Hugo / 11ty**: pure SSG, sub-second build, no JS runtime.
- **WordPress (headless)**: editorial workflow + Astro/Next frontend.
### 매 2026 GEO consideration
- AI overview (Google SGE, Perplexity, ChatGPT search) 의 citation 의 desired — clear `<h1>`, summary paragraph, bulleted facts, schema markup.
- LLM-friendly: clean semantic HTML, llms.txt 의 publishing.
### 매 응용
1. SaaS marketing site (homepage + features + pricing + blog).
2. Tech blog with MDX + code highlighting.
3. Documentation site (Mintlify, Nextra, Starlight).
4. Local business + multi-region landing pages.
## 💻 패턴
### Astro 의 blog setup
```astro
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
import Layout from '@/layouts/Article.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Layout
title={post.data.title}
description={post.data.description}
canonical={`https://site.com/blog/${post.slug}`}
ogImage={post.data.ogImage}
>
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
</Layout>
```
### JSON-LD (Article schema)
```tsx
// components/ArticleSchema.tsx
export function ArticleSchema({ post }: { post: Post }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: { '@type': 'Person', name: post.author },
image: post.ogImage,
}),
}}
/>
);
}
```
### Sitemap (Next.js App Router)
```ts
// app/sitemap.ts
import type { MetadataRoute } from 'next';
import { getAllPosts } from '@/lib/posts';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts();
return [
{ url: 'https://site.com', lastModified: new Date(), priority: 1 },
...posts.map((p) => ({
url: `https://site.com/blog/${p.slug}`,
lastModified: p.updatedAt,
changeFrequency: 'monthly' as const,
priority: 0.7,
})),
];
}
```
### Robots + canonical (Next.js metadata)
```ts
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.summary,
alternates: { canonical: `/blog/${post.slug}` },
openGraph: {
title: post.title,
description: post.summary,
images: [post.ogImage],
type: 'article',
},
twitter: { card: 'summary_large_image' },
};
}
```
### LCP 최적화 (preload hero image)
```html
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<img src="/hero.avif" alt="..." width="1200" height="630" loading="eager" decoding="async" />
```
### MDX + content collection (Astro)
```ts
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
export const collections = {
blog: defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string().max(160),
publishedAt: z.date(),
author: z.string(),
tags: z.array(z.string()),
ogImage: z.string().optional(),
}),
}),
};
```
### llms.txt (2026 GEO)
```txt
# https://site.com/llms.txt
# Site: Acme Inc. — SaaS for X
# License: CC-BY-4.0 for blog content
# Index:
- /blog/* — engineering articles
- /docs/* — product documentation
- /pricing — current pricing
```
### CWV 의 monitoring (Vercel Speed Insights / web-vitals)
```ts
import { onLCP, onINP, onCLS } from 'web-vitals';
function send(metric: any) {
navigator.sendBeacon('/api/vitals', JSON.stringify(metric));
}
onLCP(send);
onINP(send);
onCLS(send);
```
## 매 결정 기준
| 상황 | Stack |
|---|---|
| Pure content / blog | **Astro** (Islands, near-zero JS) |
| Marketing + app shell | **Next.js (App Router, SSG/ISR)** |
| Editorial team (CMS) | **Headless WP / Sanity + Astro** |
| Docs site | **Starlight / Nextra / Mintlify** |
| Multi-region SEO | Next.js + i18n routing + hreflang |
**기본값**: Astro + MDX + Vercel/Netlify deploy + Sanity/Contentlayer (if CMS needed).
## 🔗 Graph
- 부모: [[SEO]] · [[프론트엔드_및_UIUX_표준|Frontend-Architecture]]
- 응용: [[Astro]] · [[MDX]]
- Adjacent: [[Core Web Vitals Optimization (INP, LCP 개선)|Core-Web-Vitals]]
## 🤖 LLM 활용
**언제**: marketing site, blog, docs, public-facing SEO-critical content, GEO optimization.
**언제 X**: authenticated app (SPA), real-time UI (SPA/SSR), no SEO requirement.
## ❌ 안티패턴
- **SPA 의 marketing site**: crawlable HTML 의 부재 — SSG/SSR 의 mandatory.
- **Render-blocking JS / fonts**: LCP 의 destroy — `font-display: swap`, defer non-critical JS.
- **Image 의 dimensions 의 unspecified**: CLS regression — `width`/`height` 의 항상 의 specify.
- **Duplicate content 의 canonical 의 부재**: SEO penalty — `<link rel="canonical">` 의 필수.
- **Schema.org 없음**: rich result + AI overview citation 의 lose — JSON-LD 의 add.
- **Sitemap 의 stale**: ISR / on-demand revalidate or build-time regenerate.
- **GEO ignore**: AI search traffic 의 2026 의 surge — llms.txt + clear semantic HTML.
## 🧪 검증 / 중복
- Verified (Google Search Central, web.dev CWV, Astro/Next.js 2026 docs, llms.txt spec).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full 2026 SEO playbook with GEO + Astro/Next stack |
@@ -0,0 +1,202 @@
---
id: wiki-2026-0508-spa-single-page-application
title: SPA (Single Page Application)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [single-page-app, client-side-routing-app]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, spa, react, vue, routing, architecture]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react-vue-svelte
---
# SPA (Single Page Application)
## 매 한 줄
> **"매 single HTML shell 의 의 client-side routing + dynamic rendering 의 driven 의 web app 의 architecture."**. 매 2010s 의 의 dominant pattern (AngularJS, Backbone, Ember → React, Vue, Angular), 매 2026 의 RSC / SSR / Islands 의 ascendency 의 의 SPA 의 niche 화 — 매 highly-interactive dashboards, internal tools, complex stateful UIs 의 의 the right tool 이며 marketing/content 의 의 의 SSG/SSR 의 권장.
## 매 핵심
### 매 정의
- 1 HTML document 의 load — subsequent navigation 의 JS 의 driven (no full page reload).
- Client-side router 의 URL ↔ component tree 의 mapping.
- Data 의 fetch via XHR/Fetch — JSON API or RPC.
- State 의 client 의 in-memory (Redux, Zustand, Jotai, TanStack Query).
### 매 trade-offs
- **Pros**: snappy in-app navigation, rich interactivity, app-like UX, shared state across views.
- **Cons**: SEO 의 weak (without SSR), initial bundle 의 large, white screen 의 risk, accessibility 의 careful 의 wiring.
### 매 vs. modern alternatives
- **SSR / SSG (Next.js, Remix)**: server 의 HTML 의 generate, hydration 의 client interactivity 의 add.
- **RSC (React Server Components, 2026 mainstream)**: server-only components + client islands.
- **Islands (Astro, Fresh, 11ty)**: static HTML + targeted hydration 의 islands.
- **MPA (Multi-Page App)**: traditional server-rendered pages — Hotwire / Inertia 의 의 modern revival.
### 매 응용
1. Internal admin dashboards (high interactivity, no SEO need).
2. Authenticated SaaS apps (Linear, Figma, Notion).
3. Real-time collaboration (CRDT-driven, websocket-heavy).
4. Browser-based tools (Excalidraw, Tldraw).
## 💻 패턴
### React Router (data router, 2026)
```tsx
import { createBrowserRouter, RouterProvider } from 'react-router';
const router = createBrowserRouter([
{
path: '/',
Component: Layout,
children: [
{ index: true, Component: Home },
{ path: 'projects/:id', Component: Project, loader: projectLoader },
{ path: '*', Component: NotFound },
],
},
]);
export const App = () => <RouterProvider router={router} />;
```
### Data fetching (TanStack Query)
```tsx
import { useQuery } from '@tanstack/react-query';
export function ProjectView({ id }: { id: string }) {
const { data, isPending, error } = useQuery({
queryKey: ['project', id],
queryFn: () => fetch(`/api/projects/${id}`).then((r) => r.json()),
staleTime: 60_000,
});
if (isPending) return <Skeleton />;
if (error) return <ErrorBoundary error={error} />;
return <ProjectDetail project={data} />;
}
```
### Client state (Zustand)
```ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useUIStore = create(
persist<{ sidebar: boolean; toggle: () => void }>(
(set) => ({
sidebar: true,
toggle: () => set((s) => ({ sidebar: !s.sidebar })),
}),
{ name: 'ui-store' },
),
);
```
### Code splitting (lazy routes)
```tsx
import { lazy, Suspense } from 'react';
const Settings = lazy(() => import('./routes/Settings'));
const router = createBrowserRouter([
{
path: '/settings',
element: (
<Suspense fallback={<RouteSkeleton />}>
<Settings />
</Suspense>
),
},
]);
```
### History API (without router)
```ts
window.history.pushState({}, '', '/projects/42');
window.dispatchEvent(new PopStateEvent('popstate'));
window.addEventListener('popstate', () => {
render(parseRoute(location.pathname));
});
```
### Auth guard (route loader)
```tsx
async function projectLoader({ params }: LoaderFunctionArgs) {
const session = await getSession();
if (!session) throw redirect('/login');
return fetch(`/api/projects/${params.id}`).then((r) => r.json());
}
```
### Vite SPA의 setup
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: { sourcemap: true, target: 'es2022' },
server: { historyApiFallback: true },
});
```
### SEO 의 fallback (prerender + hydrate)
```ts
// vite-plugin-prerender
import prerender from 'vite-plugin-prerender';
export default {
plugins: [
react(),
prerender({ routes: ['/', '/about', '/pricing'] }),
],
};
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Marketing site, blog | **SSG** (Astro, Next.js static, 11ty) |
| Content site with personalization | **SSR** (Next.js, Remix) |
| Dashboards, admin tools | **SPA** (React Router + Vite) |
| Highly interactive editor | **SPA** with code-splitting |
| Public app needing SEO + interactivity | **Next.js (RSC + client islands)** |
| MPA-style with sprinkles | **Hotwire / Inertia.js** |
**기본값**: 2026 의 default 는 **Next.js / Remix (SSR + RSC)** — pure SPA 는 internal tools / authenticated apps 의 으로 의 reserve.
## 🔗 Graph
- 부모: [[프론트엔드_및_UIUX_표준|Frontend-Architecture]]
- 변형: [[SSR]] · [[SSG]] · [[Islands-Architecture]] · [[React-Server-Components]]
- Adjacent: [[Vite]] · [[Code-Splitting]]
## 🤖 LLM 활용
**언제**: highly-interactive authenticated app, no SEO requirement, complex client state, real-time collaboration.
**언제 X**: marketing/content site (SSG), public SEO-critical content (SSR/RSC), simple form-driven app (MPA).
## 안티패턴
- **SPA 의 marketing site**: SEO 의 weak, LCP 의 poor — SSG 의 사용.
- **Bundle 의 single chunk**: route-based code splitting 의 default.
- **Auth state 의 localStorage 의 raw token**: HttpOnly cookie + refresh flow 의 사용.
- **Client routing 의 server fallback 없음**: 404 의 deep link 의 — `historyApiFallback` / catch-all rewrite.
- **No skeleton / suspense**: white screen on slow data — Suspense + skeletons.
- **Global state 의 overuse**: server state 의 TanStack Query, UI state 의 Zustand/Jotai 의 separate.
## 🧪 검증 / 중복
- Verified (React Router 7 docs, Vue Router 4, MDN SPA architecture, web.dev rendering patterns 2026).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full SPA architecture with 2026 trade-offs vs RSC/Islands |
@@ -0,0 +1,239 @@
---
id: wiki-2026-0508-saas-대시보드-및-이커머스-레이아웃-구축
title: SaaS 대시보드 및 이커머스 레이아웃 구축
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [SaaS Dashboard Layout, Ecommerce Layout, Admin Layout]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, layout, saas, dashboard, ecommerce, nextjs, shadcn]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Next.js 16 / Tailwind v4 / shadcn-ui
---
# SaaS 대시보드 및 이커머스 레이아웃 구축
## 매 한 줄
> **"매 SaaS 의 sidebar+content shell + 매 Ecommerce 의 PLP/PDP/Cart"**. 2026 stack 매 Next.js 16 (App Router) + Tailwind v4 + shadcn-ui + Radix Primitive + TanStack Query. 매 Server Component 로 data fetch, Client Component 로 interactivity.
## 매 핵심
### 매 SaaS Dashboard 패턴
- **Shell**: sidebar (collapsible) + topbar (search/user menu) + content area.
- **Navigation**: 매 nested section, persistent state, breadcrumb.
- **Data widgets**: 매 stat card, chart (Recharts/Tremor v3), table (TanStack Table).
- **Multi-tenant**: 매 workspace switcher.
### 매 Ecommerce 패턴
- **PLP (Product List Page)**: 매 grid + filter sidebar + sort + pagination/infinite scroll.
- **PDP (Product Detail Page)**: 매 image gallery + variant selector + add-to-cart.
- **Cart/Checkout**: 매 cart drawer + multi-step checkout + Stripe Elements.
- **Search**: 매 typeahead + facet filtering (Algolia/MeiliSearch).
### 매 Stack (2026)
- Framework: Next.js 16 App Router / Remix 3 / Astro 5.
- UI: shadcn-ui v2 + Radix + Tailwind v4 (Oxide engine).
- Data: TanStack Query 5 + Server Action / RSC.
- Forms: react-hook-form + Zod.
- Tables: TanStack Table v8.
- Charts: Tremor v3 / Recharts.
### 매 응용
1. SaaS admin: Linear-style sidebar.
2. Ecommerce: Shopify Hydrogen / Next.js Commerce 템플릿.
3. Internal tool: Retool-like 매 form/table heavy.
## 💻 패턴
### App Router Layout (Next.js 16)
```tsx
// app/(dashboard)/layout.tsx
import { Sidebar } from '@/components/sidebar';
import { Topbar } from '@/components/topbar';
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const user = await getCurrentUser();
return (
<div className="grid h-screen grid-cols-[260px_1fr]">
<Sidebar />
<div className="flex flex-col overflow-hidden">
<Topbar user={user} />
<main className="flex-1 overflow-auto p-6">{children}</main>
</div>
</div>
);
}
```
### shadcn-ui Sidebar (collapsible)
```tsx
'use client';
import { Sidebar, SidebarContent, SidebarMenu, SidebarMenuItem } from '@/components/ui/sidebar';
import { Home, Users, Settings } from 'lucide-react';
const items = [
{ title: 'Home', url: '/', icon: Home },
{ title: 'Users', url: '/users', icon: Users },
{ title: 'Settings', url: '/settings', icon: Settings },
];
export function AppSidebar() {
return (
<Sidebar collapsible="icon">
<SidebarContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<a href={item.url}>
<item.icon className="size-4" />
<span>{item.title}</span>
</a>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarContent>
</Sidebar>
);
}
```
### Stat Card Grid
```tsx
function StatCards({ stats }: { stats: Stat[] }) {
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
{stats.map((s) => (
<div key={s.id} className="rounded-lg border bg-card p-6">
<div className="text-sm text-muted-foreground">{s.label}</div>
<div className="mt-2 text-3xl font-semibold">{s.value}</div>
<div className="mt-1 text-sm text-emerald-600">{s.delta}</div>
</div>
))}
</div>
);
}
```
### TanStack Table (server pagination)
```tsx
'use client';
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
import { useQuery } from '@tanstack/react-query';
export function UsersTable() {
const [page, setPage] = useState(0);
const { data } = useQuery({
queryKey: ['users', page],
queryFn: () => fetch(`/api/users?page=${page}`).then(r => r.json()),
});
const table = useReactTable({
data: data?.users ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
pageCount: data?.totalPages,
});
return <table>...</table>;
}
```
### Ecommerce PLP (RSC + filter)
```tsx
// app/products/page.tsx
export default async function ProductsPage({ searchParams }: { searchParams: Promise<{ category?: string; sort?: string }> }) {
const params = await searchParams;
const products = await getProducts({ category: params.category, sort: params.sort });
return (
<div className="grid grid-cols-[240px_1fr] gap-6">
<FilterSidebar />
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
{products.map(p => <ProductCard key={p.id} product={p} />)}
</div>
</div>
);
}
```
### Cart Drawer (Zustand + shadcn Sheet)
```tsx
'use client';
import { create } from 'zustand';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
const useCart = create<{ items: Item[]; add: (i: Item) => void; remove: (id: string) => void }>((set) => ({
items: [],
add: (i) => set((s) => ({ items: [...s.items, i] })),
remove: (id) => set((s) => ({ items: s.items.filter(x => x.id !== id) })),
}));
export function CartDrawer() {
const items = useCart((s) => s.items);
return (
<Sheet>
<SheetTrigger>Cart ({items.length})</SheetTrigger>
<SheetContent>
{items.map(i => <div key={i.id}>{i.name}</div>)}
</SheetContent>
</Sheet>
);
}
```
### Server Action (Add to cart)
```tsx
// app/actions/cart.ts
'use server';
import { cookies } from 'next/headers';
export async function addToCart(productId: string, qty: number) {
const cart = (await cookies()).get('cart');
// ... persist
return { success: true };
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| New SaaS dashboard | Next.js 16 + shadcn + Tailwind v4 |
| Ecommerce | Next.js Commerce 또는 Shopify Hydrogen |
| Internal admin | Refine.dev / Retool |
| Mobile-first ecommerce | Astro + Solid Islands |
| Real-time dashboard | Next.js + WebSocket (Pusher/Ably) |
| Heavy data viz | Tremor + Recharts |
**기본값**: 매 SaaS 매 Next.js + shadcn-ui + Tailwind v4. 매 Ecommerce 매 Next.js Commerce 템플릿 fork.
## 🔗 Graph
- 부모: [[Frontend_Architecture]]
- Adjacent: [[Next.js]] · [[Tailwind_v4]] · [[shadcn-ui]]
## 🤖 LLM 활용
**언제**: greenfield SaaS / ecommerce 의 layout shell 구축, design system 적용.
**언제 X**: 매 micro-frontend / heavy SSR-disabled 의 SPA — 매 다른 stack (Vite + React Router) 적합.
## ❌ 안티패턴
- **모든 component 'use client'**: 매 RSC 의 benefit 상실 — server-first.
- **inline style + Tailwind 혼재**: 매 inconsistency — design token 통일.
- **Custom UI library scratch**: 매 shadcn-ui copy-paste 가 maintenance 우위.
- **Cart in localStorage only**: 매 cross-device sync 불가 — server-side cart + cookie.
- **No skeleton during loading**: 매 CLS / UX 저하 — Suspense + skeleton.
## 🧪 검증 / 중복
- Verified (Next.js 16 docs, shadcn-ui v2, Tailwind v4, Vercel Commerce).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Next.js 16 / shadcn / Tailwind v4 modern stack |
@@ -0,0 +1,204 @@
---
id: wiki-2026-0508-scripts
title: Scripts
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [npm scripts, package.json scripts, Build Scripts]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [npm, build, automation, package-json]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Node.js + npm
---
# Scripts
## 매 한 줄
> **"매 scripts는 package.json 의 entry point 인 명령어 alias"**. `npm run <name>` 으로 실행되며, dev/build/test/lint/deploy 등의 lifecycle automation 을 정의. 2026 기준 npm/pnpm/bun/yarn 호환, 매 monorepo 에서는 turbo/nx 가 orchestration.
## 매 핵심
### 매 lifecycle
- **pre/post**: `prebuild``build``postbuild` 자동 실행
- **special names**: `start`, `test`, `install`, `prepare`
- **arbitrary**: 매 다른 이름 은 `npm run <name>`
### 매 cross-platform
- `cross-env`, `rimraf`, `mkdirp` 등으로 OS 차이 흡수
- 매 modern 대안: zx, execa, tsx-based scripts
### 매 응용
1. Local dev: `dev`, `build`, `test`, `lint`.
2. CI: `ci:test`, `ci:build`, `ci:deploy`.
3. Tooling: `typecheck`, `format`, `analyze`.
4. Release: `release`, `publish:dry`.
## 💻 패턴
### Modern package.json scripts
```json
{
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:e2e": "playwright test",
"test:coverage": "vitest run --coverage",
"format": "biome format --write .",
"clean": "rimraf .next dist coverage",
"prepare": "husky",
"release": "changeset publish"
}
}
```
### Pre/post hooks
```json
{
"scripts": {
"prebuild": "npm run typecheck && npm run lint",
"build": "tsc -p tsconfig.build.json",
"postbuild": "node scripts/copy-assets.mjs"
}
}
```
### Composing with `npm-run-all` or `concurrently`
```json
{
"scripts": {
"dev": "concurrently -n web,api -c blue,green \"npm:dev:web\" \"npm:dev:api\"",
"dev:web": "next dev",
"dev:api": "tsx watch server/index.ts",
"verify": "run-p typecheck lint test"
}
}
```
### TypeScript script with tsx
```typescript
// scripts/seed.ts
import { db } from "../src/lib/db";
import { users } from "../src/lib/db/schema";
await db.insert(users).values([
{ email: "alice@example.com" },
{ email: "bob@example.com" },
]);
console.log("매 seed complete");
```
```json
{ "scripts": { "db:seed": "tsx scripts/seed.ts" } }
```
### zx automation
```javascript
#!/usr/bin/env zx
import "zx/globals";
const branches = (await $`git branch --merged main`).stdout
.split("\n")
.map((b) => b.trim())
.filter((b) => b && !b.startsWith("*") && b !== "main");
for (const b of branches) {
await $`git branch -d ${b}`;
}
echo`매 deleted ${branches.length} merged branches`;
```
### Cross-platform env
```json
{
"scripts": {
"build:prod": "cross-env NODE_ENV=production webpack",
"test:debug": "cross-env DEBUG=app:* vitest"
}
}
```
### Monorepo with pnpm + turbo
```json
// root package.json
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --parallel",
"test": "turbo run test --filter=...[origin/main]"
}
}
```
### Bun-native scripts (faster startup)
```json
{
"scripts": {
"dev": "bun --hot src/index.ts",
"test": "bun test",
"build": "bun build src/index.ts --outdir dist --target node"
}
}
```
### CI matrix script
```json
{
"scripts": {
"ci:lint": "biome ci .",
"ci:typecheck": "tsc --noEmit",
"ci:test": "vitest run --coverage --reporter=verbose",
"ci:build": "next build",
"ci": "run-s ci:lint ci:typecheck ci:test ci:build"
}
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| 매 simple alias | npm script directly |
| 매 multi-step orchestration | npm-run-all / concurrently |
| Complex shell scripting | zx / bash file |
| TS logic | tsx / bun + TS file |
| Monorepo | turbo / nx |
**기본값**: npm scripts + tsx for TS + concurrently for parallel. 매 monorepo 면 turbo.
## 🔗 Graph
- 부모: [[Node.js]]
- 변형: [[Task]] · [[Turbo]] · [[Nx]]
- 응용: [[Husky Git Hooks]]
- Adjacent: [[tsx]] · [[bun]]
## 🤖 LLM 활용
**언제**: package.json 의 standard automation, dev/build/test/lint pipeline.
**언제 X**: 매 100+ lines of bash logic — 매 standalone script file 로 추출.
## ❌ 안티패턴
- **Inline complex shell**: `&& if [[ ... ]]; then ... fi` in JSON → 매 unreadable. zx / bash file.
- **No clean script**: 매 stale build artifact debug 어려움. `clean` 필수.
- **OS-specific commands**: `rm -rf` Windows 실패 → cross-platform 도구.
- **Hidden side effects in postinstall**: 매 supply-chain risk. 신중.
- **Duplicate scripts across packages**: 매 monorepo 면 turbo / shared config.
## 🧪 검증 / 중복
- Verified (npm docs, package.json spec).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — npm scripts full content |
@@ -0,0 +1,228 @@
---
id: wiki-2026-0508-server-side-rendering-ssr
title: Server Side Rendering (SSR)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [SSR, Server Rendering, isomorphic rendering]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
verification_status: applied
tags: [ssr, react, nextjs, rendering, performance]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: Next.js 15 / React 19
---
# Server Side Rendering (SSR)
## 매 한 줄
> **"매 SSR 은 server 에서 HTML 을 생성해 first paint 를 빠르게, SEO 를 가능하게"**. 2026 기준 React 19 의 streaming SSR + Suspense + Server Components 가 표준. 매 trade-off 는 server compute cost vs CSR-only 의 blank-screen 제거. 매 modern 변형: SSG (build-time), ISR (revalidate), PPR (Partial Prerender).
## 매 핵심
### 매 rendering modes
- **CSR**: 매 client only — slow first paint, no SEO without JS
- **SSR**: 매 server-render full HTML per request
- **SSG**: 매 build-time HTML (static)
- **ISR**: 매 SSG + on-demand or time-based revalidation
- **PPR**: 매 partial prerender — static shell + dynamic holes (Next.js 15)
### 매 hydration
- Server HTML 송신 → client JS 가 attach (event handler 연결)
- Streaming SSR: HTML 을 chunk 로 진행 송신
- Selective hydration: visible / interacted 부분 우선
### 매 응용
1. Marketing / blog (SEO + fast paint).
2. E-commerce PDP (per-user pricing + SEO).
3. Dashboard shells (PPR — static shell + dynamic data).
## 💻 패턴
### Next.js 15 RSC + streaming
```tsx
// app/page.tsx
import { Suspense } from "react";
import { ProductGrid } from "./product-grid";
export default function Page() {
return (
<main>
<h1>Products</h1>
<Suspense fallback={<div> loading</div>}>
<ProductGrid />
</Suspense>
</main>
);
}
// app/product-grid.tsx (RSC, runs on server)
import { db } from "@/lib/db";
export async function ProductGrid() {
const products = await db.product.findMany({ take: 20 });
return (
<ul>
{products.map((p) => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
```
### Partial Prerendering (PPR)
```tsx
// app/dashboard/page.tsx
export const experimental_ppr = true;
import { Suspense } from "react";
import { StaticHeader } from "./header";
import { LiveMetrics } from "./metrics";
export default function Page() {
return (
<>
<StaticHeader /> {/* prerendered */}
<Suspense fallback={<MetricsSkeleton />}>
<LiveMetrics /> {/* dynamic, streamed */}
</Suspense>
</>
);
}
```
### ISR with revalidate
```tsx
// app/posts/[slug]/page.tsx
export const revalidate = 60; // every 60s
export default async function Page({
params,
}: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await fetch(`https://api.example.com/posts/${slug}`,
{ next: { revalidate: 60, tags: [`post:${slug}`] } }).then((r) => r.json());
return <article>{post.title}</article>;
}
```
### On-demand revalidation
```typescript
// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
export async function POST(req: Request) {
const { tag } = await req.json();
revalidateTag(tag);
return Response.json({ ok: true });
}
```
### Pure React 19 streaming SSR
```typescript
// server.ts
import { renderToReadableStream } from "react-dom/server";
import App from "./App";
export default async function handler(req: Request): Promise<Response> {
const stream = await renderToReadableStream(<App url={req.url} />, {
bootstrapModules: ["/client.js"],
onError: (err) => console.error(err),
});
await stream.allReady; // remove for true streaming
return new Response(stream, {
headers: { "content-type": "text/html" },
});
}
```
### Client hydration entry
```typescript
// client.ts
import { hydrateRoot } from "react-dom/client";
import App from "./App";
hydrateRoot(document, <App url={location.href} />);
```
### Cache headers for SSR
```typescript
// app/api/data/route.ts
export async function GET() {
return Response.json(
{ data: 42 },
{
headers: {
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
},
},
);
}
```
### Server-only utility
```typescript
// lib/server-only.ts
import "server-only"; // 매 import in client component throws
import { db } from "./db";
export async function loadSecret() {
return db.secret.findFirst();
}
```
### Edge runtime SSR
```typescript
// app/page.tsx
export const runtime = "edge";
export default async function Page() {
const data = await fetch("https://api.example.com/edge").then((r) => r.json());
return <pre>{JSON.stringify(data)}</pre>;
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Static marketing page | SSG (`generateStaticParams`) |
| Per-user dynamic | SSR / RSC |
| Mostly static + small dynamic | **PPR** |
| Data refreshes minutes-scale | ISR with revalidate |
| Internal app, no SEO | CSR (Vite SPA) sufficient |
| Low latency global | Edge runtime SSR |
**기본값**: Next.js 15 App Router + RSC + Suspense streaming + PPR where applicable.
## 🔗 Graph
- 부모: [[Rendering Strategies]] · [[Web Performance]]
- 변형: [[CSR]] · [[SSG]] · [[ISR]] · [[Streaming SSR]]
- 응용: [[Remix]] · [[SvelteKit]] · [[Nuxt]]
- Adjacent: [[React Server Components]] · [[Hydration]] · [[Suspense]]
## 🤖 LLM 활용
**언제**: rendering strategy 결정, SEO + first paint 최적화, RSC + Suspense 설계.
**언제 X**: 매 internal admin tool with auth-only access (CSR 충분), 매 매우 dynamic real-time app (WebSocket-driven).
## ❌ 안티패턴
- **SSR everything**: 매 unnecessary server compute. 매 marketing page → SSG.
- **No streaming**: 매 await all data → blank for 5s. 매 Suspense + streaming.
- **Hydration mismatch**: server `Date.now()` vs client → 매 warning. `suppressHydrationWarning` 또는 client-only render.
- **Secret in client component**: 매 env var leak. `server-only` import.
- **Massive RSC payload**: 매 props 에 huge JSON. 매 boundary 재설계.
- **Forgetting cache tags**: ISR 인데 invalidation 못 함.
## 🧪 검증 / 중복
- Verified (Next.js 15 docs, React 19 docs, Vercel blog 2026).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — SSR full content |
@@ -0,0 +1,181 @@
---
id: wiki-2026-0508-spectre
title: Spectre
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Spectre Attack, CVE-2017-5753, CVE-2017-5715, Branch Target Injection]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [security, cpu, side-channel, speculation, microarchitecture]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: c
framework: x86-arm
---
# Spectre
## 매 한 줄
> **"매 speculative execution 의 microarchitectural side-effect leak"**. 2018 Kocher et al. discovery — branch predictor / BTB 를 mistrained 시켜 out-of-bounds load 의 cache footprint 로 secret 을 추출. 2026 현재 Variant 1/2/4 + Retbleed/Inception 등 8년차 ongoing mitigation arms race.
## 매 핵심
### 매 Variants
- **V1 (Bounds Check Bypass, CVE-2017-5753)**: speculative array access past bounds.
- **V2 (Branch Target Injection, CVE-2017-5715)**: BTB poisoning — indirect call gadget hijack.
- **V4 (Speculative Store Bypass, CVE-2018-3639)**: store-to-load forwarding misprediction.
- **Retbleed (2022)**: return predictor 의 V2 variant.
- **Inception (2023)**: AMD Zen recursive speculation.
### 매 Mechanism
- **Speculation**: CPU executes past branch before resolution.
- **Transient window**: ~100-200 cycles before rollback.
- **Covert channel**: cache (Flush+Reload) / port contention / TLB.
- **Architectural state**: rolled back. Microarchitectural: persists.
### 매 응용
1. JS sandbox escape (browser → cross-origin memory read).
2. KVM guest → host memory leak.
3. Kernel ASLR break + secret exfiltration.
## 💻 패턴
### V1 gadget (classic)
```c
// Vulnerable: array2 cache state leaks array1[x]
uint8_t array1[16];
uint8_t array2[256 * 4096];
void victim(size_t x) {
if (x < 16) { // mistrained branch
uint8_t v = array1[x]; // x can be OOB during speculation
uint8_t leak = array2[v * 4096]; // cache footprint encodes v
}
}
// Attacker: train with valid x, then call with OOB x,
// flush array2, victim(), then time array2[i*4096] reads.
```
### Flush+Reload primitive
```c
#include <x86intrin.h>
static inline uint64_t rdtsc_serial(void) {
_mm_lfence();
uint64_t t = __rdtsc();
_mm_lfence();
return t;
}
int probe(volatile uint8_t *addr) {
uint64_t t0 = rdtsc_serial();
(void)*addr;
uint64_t t1 = rdtsc_serial();
_mm_clflush((void *)addr);
return (t1 - t0) < CACHE_HIT_THRESHOLD; // ~80 cycles
}
```
### V1 mitigation: lfence barrier
```c
void victim_safe(size_t x) {
if (x < 16) {
_mm_lfence(); // serialize — block speculation
uint8_t v = array1[x];
uint8_t leak = array2[v * 4096];
}
}
// Cost: ~30-50% perf hit. 매 array_index_nospec() 의 Linux kernel 사용.
```
### V1 mitigation: index masking
```c
// Linux kernel array_index_nospec
static inline size_t mask_idx(size_t idx, size_t sz) {
size_t mask = ~(idx >= sz ? ~0UL : 0);
return idx & mask; // 0 if OOB, idx otherwise — branchless
}
void victim_masked(size_t x) {
if (x < 16) {
x = mask_idx(x, 16);
uint8_t v = array1[x];
uint8_t leak = array2[v * 4096];
}
}
```
### V2 mitigation: retpoline
```asm
; Replace `jmp *%rax` with retpoline trampoline
retpoline:
call set_up_target
capture:
pause
lfence
jmp capture ; speculation trap
set_up_target:
mov %rax, (%rsp) ; overwrite return addr
ret ; predictor uses RSB, not BTB
```
### Browser mitigation: timer coarsening
```js
// performance.now() resolution reduced from 5μs → 100μs (Chrome 2018+).
// Cross-origin isolation (COOP+COEP) required for SharedArrayBuffer.
performance.now(); // 1234.1 (was 1234.123456)
// SharedArrayBuffer gated on:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp
```
### Site Isolation (Chrome)
```text
- Each origin → separate renderer process.
- OS-level memory boundary blocks Spectre cross-origin reads.
- Cost: +10-20% memory.
- Partial Site Isolation on Android (resource-constrained).
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Kernel hot-path bounds check | array_index_nospec (mask) |
| Indirect call (kernel/hypervisor) | Retpoline + IBRS/eIBRS |
| JS engine bounds check | index masking + speculation barrier |
| Browser cross-origin | Site Isolation + COOP/COEP + timer coarsening |
| Embedded / no MMU | accept risk, no speculation typically |
**기본값**: hardware mitigation (eIBRS, IBPB, BHI_DIS_S) on by default + retpoline + array_index_nospec on hot paths.
## 🔗 Graph
- 부모: [[Side Channel Attack]]
- 변형: [[Spectre|Spectre and Meltdown]]
- 응용: [[Speculative Execution]]
- Adjacent: [[Cache Timing Attack]] · [[Timing Attack]]
## 🤖 LLM 활용
**언제**: explaining microarchitectural attacks, kernel mitigation review, browser sandbox design, audit speculation gadgets.
**언제 X**: high-level web app security (XSS/CSRF) — Spectre 의 ~irrelevant in app layer; OS+browser handle it.
## ❌ 안티패턴
- **lfence everywhere**: 30-50% perf hit. Use array_index_nospec mask instead.
- **Disable speculation entirely**: 5-10x slowdown. Never deploy.
- **Trust performance.now() resolution alone**: SharedArrayBuffer 의 still risk without COOP/COEP.
- **Ignore V2 retpoline 의 ROP risk**: RSB stuffing required on context switch.
## 🧪 검증 / 중복
- Verified (Kocher et al. 2018 paper, Intel/AMD security advisories, Linux kernel `Documentation/admin-guide/hw-vuln/`).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full canonical (V1/V2/V4 + retpoline/lfence/mask + browser isolation) |
@@ -0,0 +1,250 @@
---
id: wiki-2026-0508-state-management-libraries
title: State Management Libraries
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [State Management, React State Libraries, Zustand vs Jotai vs Redux]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
verification_status: applied
tags: [state-management, react, zustand, jotai, valtio, redux]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React 19
---
# State Management Libraries
## 매 한 줄
> **"매 state management 는 component tree 외부의 reactive store 추상화"**. 2026 기준 React server-side data 는 TanStack Query / RSC, client-only state 는 Zustand (top-down) / Jotai (bottom-up atomic) / Valtio (proxy mutable) 가 mainstream. Redux 는 legacy + 매우 큰 enterprise 만. 매 핵심 결정은 "어떤 abstraction 이 mental model 과 맞는가".
## 매 핵심
### 매 4 가지 패러다임
- **Top-down store** (Zustand, Redux): 단일 store, selector
- **Atomic** (Jotai, Recoil): 작은 atom 의 graph
- **Proxy mutable** (Valtio, MobX): mutable object, auto-tracking
- **Server-state** (TanStack Query, SWR): cache + revalidation
### 매 server vs client
- 2026 의 truth: 매 대부분의 "state" 는 사실 server data → 매 TanStack Query 로
- 진짜 client state (modal open, form draft, theme) → 매 Zustand / Jotai
### 매 응용
1. Zustand: 매 일반 SPA, mid-size app 의 default.
2. Jotai: 매 fine-grained reactivity, derived value graph.
3. Valtio: 매 game/canvas, mutable mental model 선호.
4. Redux Toolkit: 매 legacy migration, Redux DevTools 의존.
## 💻 패턴
### Zustand store with slice pattern
```typescript
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
type AuthSlice = {
user: { id: string; name: string } | null;
login: (u: { id: string; name: string }) => void;
logout: () => void;
};
type CartSlice = {
items: { id: string; qty: number }[];
add: (id: string) => void;
};
export const useStore = create<AuthSlice & CartSlice>()(
devtools(
persist(
immer((set) => ({
user: null,
login: (u) => set((s) => { s.user = u; }),
logout: () => set((s) => { s.user = null; }),
items: [],
add: (id) =>
set((s) => {
const it = s.items.find((i) => i.id === id);
if (it) it.qty += 1;
else s.items.push({ id, qty: 1 });
}),
})),
{ name: "app-store" },
),
),
);
// usage with selector to prevent re-render
const user = useStore((s) => s.user);
```
### Jotai atomic + derived
```typescript
import { atom, useAtom, useAtomValue } from "jotai";
import { atomWithStorage } from "jotai/utils";
export const themeAtom = atomWithStorage<"light" | "dark">("theme", "light");
export const filterAtom = atom("");
export const itemsAtom = atom<{ id: string; name: string }[]>([]);
export const filteredItemsAtom = atom((get) => {
const f = get(filterAtom).toLowerCase();
return get(itemsAtom).filter((i) => i.name.toLowerCase().includes(f));
});
function Search() {
const [filter, setFilter] = useAtom(filterAtom);
const filtered = useAtomValue(filteredItemsAtom);
return (
<>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<ul>{filtered.map((i) => <li key={i.id}>{i.name}</li>)}</ul>
</>
);
}
```
### Valtio proxy
```typescript
import { proxy, useSnapshot } from "valtio";
export const game = proxy({
player: { x: 0, y: 0, hp: 100 },
enemies: [] as { id: string; x: number; y: number }[],
damage(amount: number) {
game.player.hp = Math.max(0, game.player.hp - amount);
},
});
function HUD() {
const snap = useSnapshot(game);
return <div>HP: {snap.player.hp}</div>;
}
// mutate directly outside React
setInterval(() => {
game.player.x += 1; // 매 자동으로 컴포넌트 re-render
}, 16);
```
### Redux Toolkit (when 필요)
```typescript
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counter = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
inc: (s) => { s.value += 1; },
incBy: (s, a: { payload: number }) => { s.value += a.payload; },
},
});
export const { inc, incBy } = counter.actions;
export const store = configureStore({ reducer: { counter: counter.reducer } });
```
### TanStack Query for server state
```typescript
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
export function usePosts() {
return useQuery({
queryKey: ["posts"],
queryFn: () => fetch("/api/posts").then((r) => r.json()),
staleTime: 60_000,
});
}
export function useCreatePost() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: { title: string }) =>
fetch("/api/posts", { method: "POST", body: JSON.stringify(body) }),
onSuccess: () => qc.invalidateQueries({ queryKey: ["posts"] }),
});
}
```
### Zustand + RSC bridge (Next.js 15)
```typescript
// store-provider.tsx
"use client";
import { createContext, useContext, useRef } from "react";
import { useStore as useZ, type StoreApi } from "zustand";
import { createStore } from "zustand/vanilla";
type State = { count: number; inc: () => void };
const StoreCtx = createContext<StoreApi<State> | null>(null);
export function StoreProvider({ children, initialCount = 0 }: any) {
const ref = useRef<StoreApi<State>>();
if (!ref.current) {
ref.current = createStore<State>((set) => ({
count: initialCount,
inc: () => set((s) => ({ count: s.count + 1 })),
}));
}
return <StoreCtx.Provider value={ref.current}>{children}</StoreCtx.Provider>;
}
export function useAppStore<T>(sel: (s: State) => T): T {
const store = useContext(StoreCtx);
if (!store) throw new Error("StoreProvider missing");
return useZ(store, sel);
}
```
### Subscribing outside React (Zustand)
```typescript
const unsub = useStore.subscribe(
(s) => s.user,
(user) => console.log("user changed", user),
);
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Server data (lists, details) | TanStack Query / SWR |
| Mid-size client state, simple | **Zustand** |
| Fine-grained derived graphs | **Jotai** |
| Mutable mental model, games | Valtio |
| Legacy / strict Redux DevTools | Redux Toolkit |
| Shared single value (theme, locale) | Context (sufficient) |
**기본값**: server state → TanStack Query, client state → Zustand. Jotai 는 atom graph 가 도움될 때.
## 🔗 Graph
- 부모: [[React]] · [[Large_Frontend_Projects|Frontend Architecture]]
- 변형: [[Zustand]] · [[Jotai]] · [[Valtio]] · [[Redux Toolkit]] · [[MobX]] · [[Recoil]]
- Adjacent: [[React Server Components]]
## 🤖 LLM 활용
**언제**: client-only state design, store shape 결정, library 선택. 매 SPA 의 boundary.
**언제 X**: server data fetch (TanStack Query), 매 single-component local state (useState).
## ❌ 안티패턴
- **Redux for everything**: 매 small app 에 boilerplate. 매 Zustand 로 충분.
- **Server state in Zustand**: cache invalidation 직접 구현 → 매 TanStack Query 사용.
- **Atom 폭발**: Jotai 에서 모든 변수를 atom 으로 → 매 graph navigation 혼란.
- **No selector**: `useStore((s) => s)` 전체 구독 → 모든 변경에 re-render.
- **Mutating snapshot**: Valtio snap 을 직접 mutate → noop or warning.
- **Multiple stores for same domain**: 매 single source of truth 위배.
## 🧪 검증 / 중복
- Verified (Zustand/Jotai/Valtio/Redux Toolkit official docs, 2026).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — state management libraries canonical full content |
@@ -0,0 +1,247 @@
---
id: wiki-2026-0508-style-registry
title: Style Registry
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [SSR Style Registry, useServerInsertedHTML, CSS-in-JS SSR]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, ssr, css-in-js, nextjs, react, app-router]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: nextjs
---
# Style Registry
## 매 한 줄
> **"매 SSR streaming 시 CSS-in-JS 의 styles 를 HTML 에 inject 하는 mechanism"**. Next.js 13 App Router 의 `useServerInsertedHTML` hook 도입 — 매 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**: `useServerInsertedHTML` provides hook into Suspense boundary stream.
### 매 Mechanism
1. Server: collect styles into sheet during render (per request).
2. Server: insert `<style>` tags into HTML stream via `useServerInsertedHTML`.
3. Client: hydrate — runtime takes over, no re-render needed.
4. Concurrency: per-request sheet (no global state pollution).
### 매 응용
1. styled-components in Next.js App Router.
2. Emotion in Next.js App Router.
3. @mui v5+ in Next.js.
## 💻 패턴
### styled-components Registry
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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) |
@@ -0,0 +1,217 @@
---
id: wiki-2026-0508-styled-components-v6
title: Styled Components v6
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [styled-components, sc-v6, CSS-in-JS]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, css-in-js, react, styled-components]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react
---
# Styled Components v6
## 매 한 줄
> **"매 React-runtime CSS-in-JS 의 v6 modernization"**. 2023 release — stylis v4 (5x faster), `transient props` ($-prefix) 의 default, native `transient`, drop legacy babel-plugin in favor of SWC plugin. 2026 현재 RSC-incompatible 의 큰 단점 — Next.js App Router 사용 시 zero-runtime alternatives (Linaria/Vanilla Extract/Panda) 의 dominant.
## 매 핵심
### 매 v6 변경
- **stylis v4**: parser rewrite, 5x faster, smaller bundle.
- **`as` polymorphic prop**: typed properly with TS generics.
- **`$prop` transient**: forwarded to component but not DOM attr (was opt-in, now norm).
- **No babel-plugin needed**: SWC/Vite plugin 의 standard.
- **Drop**: `.extend`, primary-theme prop forwarding, IE11.
### 매 RSC limitation
- styled-components 의 React Server Components 와 incompatible — runtime stylesheet injection requires client.
- Next.js App Router 의 `'use client'` boundary required everywhere using styled.
- Mitigation: `StyleRegistry` + `useServerInsertedHTML` for SSR streaming.
### 매 응용
1. Design system component library (theming via ThemeProvider).
2. Conditional styling via props (variant, size).
3. Animation via `keyframes` helper.
## 💻 패턴
### Basic styled component
```typescript
import styled from 'styled-components';
const Button = styled.button<{ $primary?: boolean; $size?: 'sm' | 'md' | 'lg' }>`
padding: ${({ $size }) => ({ sm: '4px 8px', md: '8px 16px', lg: '12px 24px' }[$size ?? 'md'])};
background: ${({ $primary, theme }) => $primary ? theme.colors.primary : 'transparent'};
color: ${({ $primary, theme }) => $primary ? '#fff' : theme.colors.text};
border: 1px solid ${({ theme }) => theme.colors.border};
border-radius: 4px;
cursor: pointer;
&:hover { opacity: 0.9; }
`;
<Button $primary $size="lg">Click</Button>
```
### Theme + TypeScript
```typescript
// styled.d.ts
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
colors: { primary: string; text: string; border: string; bg: string };
space: (n: number) => string;
}
}
// theme.ts
export const theme: DefaultTheme = {
colors: { primary: '#0066cc', text: '#222', border: '#ddd', bg: '#fff' },
space: n => `${n * 4}px`,
};
<ThemeProvider theme={theme}><App /></ThemeProvider>
```
### Polymorphic `as`
```typescript
const Box = styled.div<{ $padded?: boolean }>`
padding: ${({ $padded }) => $padded ? '16px' : 0};
`;
// Render as <a> with proper typing
<Box as="a" href="/x" $padded>Link</Box>
// Component composition
const Card = styled(Box)`
border: 1px solid #ddd;
border-radius: 8px;
`;
```
### Keyframes + animation
```typescript
import styled, { keyframes } from 'styled-components';
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
const Spinner = styled.div`
width: 24px;
height: 24px;
border: 2px solid #ddd;
border-top-color: #0066cc;
border-radius: 50%;
animation: ${spin} 1s linear infinite;
`;
```
### Next.js App Router SSR setup
```typescript
// app/registry.tsx
'use client';
import { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
export function StyledRegistry({ children }: { children: React.ReactNode }) {
const [sheet] = useState(() => new ServerStyleSheet());
useServerInsertedHTML(() => {
const styles = sheet.getStyleElement();
sheet.instance.clearTag();
return <>{styles}</>;
});
if (typeof window !== 'undefined') return <>{children}</>;
return <StyleSheetManager sheet={sheet.instance}>{children}</StyleSheetManager>;
}
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html><body>
<StyledRegistry>{children}</StyledRegistry>
</body></html>
);
}
```
### Global styles + reset
```typescript
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: ${({ theme }) => theme.fonts.body};
background: ${({ theme }) => theme.colors.bg};
}
`;
```
### Variants via css helper
```typescript
import styled, { css } from 'styled-components';
const variants = {
primary: css`background: #0066cc; color: #fff;`,
ghost: css`background: transparent; border: 1px solid #0066cc; color: #0066cc;`,
danger: css`background: #cc0000; color: #fff;`,
};
const Button = styled.button<{ $variant: keyof typeof variants }>`
padding: 8px 16px;
border-radius: 4px;
${({ $variant }) => variants[$variant]}
`;
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Existing styled-components codebase | stay on v6 |
| Next.js App Router (greenfield) | Vanilla Extract / Panda CSS / Tailwind |
| Component library (npm package) | v6 fine OR Stitches/VE (zero-runtime) |
| Performance-critical (LCP) | zero-runtime CSS (Linaria/VE) |
| Need RSC | NOT styled-components |
**기본값**: 2026 greenfield React → Tailwind or Vanilla Extract. Existing styled-components codebase → upgrade to v6, plan migration if RSC needed.
## 🔗 Graph
- 부모: [[CSS in JS]]
- 변형: [[Vanilla Extract]]
- 응용: [[Style Registry]] · [[Design Tokens]] · [[Theming]]
- Adjacent: [[CSS_Architecture_and_Styling|Tailwind CSS]] · [[Panda CSS]] · [[Modern_Web_Rendering_and_Optimization|Server Components]]
## 🤖 LLM 활용
**언제**: existing SC codebase, design system library with runtime theming, Pages Router Next.js.
**언제 X**: RSC-heavy app, performance-critical (LCP), greenfield 2026 — recommend zero-runtime alternative.
## ❌ 안티패턴
- **Non-transient props leaking to DOM**: `<Button primary>``<button primary>` warning. Use `$primary`.
- **styled() inside render body**: new component each render → tree thrash + cache miss.
- **No ThemeProvider but using theme**: undefined theme errors.
- **Forgetting StyleRegistry on SSR**: FOUC + hydration mismatch.
- **Mixing styled-components and Tailwind**: specificity hell.
## 🧪 검증 / 중복
- Verified (styled-components v6 changelog, official docs, Next.js styling guide 2025).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full canonical (v6 changes + RSC limit + SSR registry + patterns) |
@@ -0,0 +1,169 @@
---
id: wiki-2026-0508-threejs-webgpu-파티클-예제
title: Threejs WebGPU 파티클 예제
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Three.js WebGPU Particles, TSL Particles]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [threejs, webgpu, particles, tsl, gpgpu]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: javascript
framework: three.js
---
# Threejs WebGPU 파티클 예제
## 매 한 줄
> **"매 GPU compute shader 의 millions-of-particles 의 60fps"**. Three.js r170+ 의 WebGPURenderer 의 TSL (Three Shader Language) 의 compute node 의 particle position/velocity 의 GPU buffer 의 simulate. 매 2026 standard: WebGPU 의 baseline browser support (Chrome/Edge/Safari 17.4+/Firefox 127+) 의 production 의 reach.
## 매 핵심
### 매 architecture
- **Storage buffer**: 매 particle position/velocity 의 GPU memory 의 persist — 매 CPU readback 의 X.
- **Compute pass**: 매 frame 의 start 의 simulation step 의 dispatch.
- **Render pass**: 매 same buffer 의 vertex attribute 의 read — instanced point / mesh.
- **TSL**: 매 JS-authored shader graph 의 WGSL 의 compile — 매 backend portability (WebGPU + WebGL fallback).
### 매 핵심 node
- `storage()`: 매 mutable GPU buffer.
- `Fn()`: 매 reusable shader function.
- `instanceIndex`: 매 compute thread id.
- `attribute()`: 매 vertex attribute read.
- `uniform()`: 매 per-frame CPU-set value.
### 매 응용
1. Galaxy / nebula simulation 의 web demo.
2. GPGPU fluid (SPH, FLIP) 의 art piece.
3. Real-time crowd / flock (boid) 의 100k+ agent.
4. Data viz 의 millions-of-points scatter.
## 💻 패턴
### Setup WebGPURenderer
```javascript
import * as THREE from 'three/webgpu';
import { Fn, storage, instanceIndex, uniform, vec3, sin, cos, time } from 'three/tsl';
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
await renderer.init();
document.body.appendChild(renderer.domElement);
```
### Allocate particle buffers
```javascript
const COUNT = 500_000;
const positionBuffer = storage(new THREE.StorageInstancedBufferAttribute(COUNT, 3), 'vec3', COUNT);
const velocityBuffer = storage(new THREE.StorageInstancedBufferAttribute(COUNT, 3), 'vec3', COUNT);
```
### Init compute (one-time)
```javascript
const initCompute = Fn(() => {
const i = instanceIndex;
const angle = i.toFloat().mul(0.001);
positionBuffer.element(i).assign(vec3(cos(angle).mul(50), 0, sin(angle).mul(50)));
velocityBuffer.element(i).assign(vec3(0));
})().compute(COUNT);
await renderer.computeAsync(initCompute);
```
### Per-frame simulation
```javascript
const dt = uniform(0.016);
const simCompute = Fn(() => {
const i = instanceIndex;
const pos = positionBuffer.element(i);
const vel = velocityBuffer.element(i);
const gravity = pos.normalize().negate().mul(9.8);
vel.addAssign(gravity.mul(dt));
pos.addAssign(vel.mul(dt));
})().compute(COUNT);
```
### Render as instanced points
```javascript
const material = new THREE.SpriteNodeMaterial();
material.positionNode = positionBuffer.toAttribute();
material.colorNode = velocityBuffer.toAttribute().length().mul(0.1);
const mesh = new THREE.InstancedMesh(new THREE.PlaneGeometry(0.05), material, COUNT);
scene.add(mesh);
```
### Animation loop
```javascript
renderer.setAnimationLoop(async () => {
dt.value = clock.getDelta();
await renderer.computeAsync(simCompute);
await renderer.renderAsync(scene, camera);
});
```
### Curl noise flow field
```javascript
import { mx_noise_vec3 } from 'three/tsl';
const flowCompute = Fn(() => {
const i = instanceIndex;
const pos = positionBuffer.element(i);
const noise = mx_noise_vec3(pos.mul(0.1).add(time.mul(0.5)));
velocityBuffer.element(i).assign(noise.mul(2));
pos.addAssign(velocityBuffer.element(i).mul(dt));
})().compute(COUNT);
```
### WebGL fallback
```javascript
const renderer = WebGPU.isAvailable()
? new THREE.WebGPURenderer()
: new THREE.WebGLRenderer();
// 매 TSL 의 same code 의 WebGL backend 의 transpile (compute X 의 limit 의 case 의 GPGPUComputationRenderer 의 fallback)
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| <10k particle, simple motion | CPU 의 BufferGeometry update |
| 10k100k, WebGL2 only | GPGPUComputationRenderer (ping-pong texture) |
| 100k10M, modern browser | WebGPURenderer + TSL compute |
| Physics-accurate fluid | WebGPU compute + custom WGSL |
| Cross-browser 의 require, IE/legacy | Canvas2D 또는 WebGL1 fallback |
**기본값**: 매 2026 의 new project 의 WebGPURenderer + TSL — 매 WebGL fallback 의 automatic.
## 🔗 Graph
- 부모: [[Three.js]] · [[WebGPU]]
- 변형: [[GPGPU]] · [[TSL Three Shader Language]]
- 응용: [[Particle System]]
- Adjacent: [[Compute Shader]] · [[Instanced Rendering]]
## 🤖 LLM 활용
**언제**: 매 100k+ particle 의 60fps 의 require, 매 modern browser 의 target.
**언제 X**: 매 static scene, low count, 또는 mobile-Safari-pre-17.4 의 fallback 의 critical.
## ❌ 안티패턴
- **CPU position update**: 매 frame 의 millions-of-vertex 의 GPU upload 의 PCIe bottleneck.
- **Compute pass 의 매 frame 의 buffer recreate**: 매 GC pressure 의 stutter — 매 reuse.
- **`renderer.compute()` sync wait**: 매 main thread 의 block — 매 `computeAsync` 의 use.
- **Float32 over-precision**: 매 WebGPU 의 f16 storage 의 bandwidth halve 의 opportunity 의 miss.
- **TSL 의 raw WGSL 의 mix 의 unnecessary**: 매 portability 의 break.
## 🧪 검증 / 중복
- Verified (Three.js r170+ docs, threejs.org/examples webgpu_compute_particles).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — TSL compute, instanced render, WebGL fallback |
@@ -0,0 +1,192 @@
---
id: wiki-2026-0508-timing-attack
title: Timing Attack
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Timing Side Channel, Constant Time Comparison]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [security, side-channel, cryptography, constant-time]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: nodejs
---
# Timing Attack
## 매 한 줄
> **"매 execution time 의 secret-dependent variation 의 leak"**. 1996 Kocher RSA timing paper origin — comparison/branch/cache 가 secret bit 에 dependent 면 attacker 가 multiple measurement 의 statistical analysis 로 secret 을 추출. Mitigation 의 핵심: constant-time code.
## 매 핵심
### 매 Vulnerable patterns
- **Early-exit string compare**: `==` / `strcmp` returns on first mismatch byte.
- **Secret-dependent branch**: `if (key[i] == 0)` 의 cache miss timing differs.
- **Secret-dependent table lookup**: AES S-box → cache line timing.
- **Modular exponentiation**: square-and-multiply 의 bit-dependent ops.
### 매 Mitigation
- **Constant-time compare**: scan all bytes regardless, XOR-accumulate.
- **No secret-dependent branches**: use bitwise mask instead.
- **No secret-dependent indices**: scan full table or use bit-slicing.
- **Blinding**: randomize input (RSA: blind w/ random r, decrypt, unblind).
### 매 응용
1. HMAC token comparison (auth bypass via timing).
2. Password hash compare (after bcrypt — still need constant-time).
3. JWT signature verify.
## 💻 패턴
### Vulnerable: early-exit compare
```typescript
// ❌ DON'T — leaks prefix length
function badCompare(a: string, b: string): boolean {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false; // early exit reveals match length
}
return true;
}
// Attacker measures: "aaaa" vs "baaa" faster than "aaaa" vs "aaab"
```
### Constant-time compare (Node.js)
```typescript
import { timingSafeEqual } from 'node:crypto';
function safeCompare(a: string, b: string): boolean {
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
if (bufA.length !== bufB.length) {
// length leak unavoidable — pad to fixed length OR accept
timingSafeEqual(bufA, bufA); // dummy compare to equalize timing
return false;
}
return timingSafeEqual(bufA, bufB);
}
// HMAC token check
import { createHmac } from 'node:crypto';
function verifyToken(token: string, payload: string, secret: string): boolean {
const expected = createHmac('sha256', secret).update(payload).digest('hex');
return expected.length === token.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(token));
}
```
### Browser: SubtleCrypto + manual constant-time
```typescript
// Web Crypto has no timingSafeEqual — implement carefully
function ctEqualBytes(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) {
diff |= a[i] ^ b[i]; // XOR-accumulate, no branch
}
return diff === 0;
}
async function verifyHmac(msg: string, sig: ArrayBuffer, key: CryptoKey) {
const computed = await crypto.subtle.sign(
'HMAC', key, new TextEncoder().encode(msg)
);
return ctEqualBytes(new Uint8Array(computed), new Uint8Array(sig));
}
```
### Python constant-time compare
```python
import hmac
def verify(token: str, expected: str) -> bool:
return hmac.compare_digest(token, expected)
# bcrypt — already constant-time via library
import bcrypt
def check_password(plain: bytes, hashed: bytes) -> bool:
return bcrypt.checkpw(plain, hashed) # safe internally
```
### Go constant-time
```go
import "crypto/subtle"
func verify(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}
// Conditional copy without branch
func ctSelect(cond int, a, b []byte) {
subtle.ConstantTimeCopy(cond, a, b)
}
```
### Branchless conditional (C-style)
```typescript
// Constant-time conditional select
function ctSelect(cond: number, a: number, b: number): number {
// cond must be 0 or 1
const mask = -cond; // 0 or 0xFFFFFFFF
return (a & mask) | (b & ~mask);
}
// Constant-time min/max without branch
function ctMin(a: number, b: number): number {
const lt = (a - b) >>> 31; // 1 if a<b
return ctSelect(lt, a, b);
}
```
### Remote timing (network attacks)
```text
Lucky 13 (TLS 2013): MAC verify timing leaked plaintext.
CRIME / BREACH: compression length leaked secrets (different side channel).
Mitigation:
- Always run full computation regardless of input validity.
- Add randomized delay (debatable — may not help, can hurt).
- Rate-limit + monitoring for anomalous timing-probe traffic.
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Password hash check | bcrypt/argon2 lib (already CT) |
| HMAC/token compare | timingSafeEqual / hmac.compare_digest |
| AES on untrusted host | AES-NI (HW) or bit-sliced soft impl |
| Secret-dependent index | bitwise mask or full-table scan |
| RSA/ECDSA private op | use vetted lib (BoringSSL, libsodium) — never roll your own |
**기본값**: never write your own crypto compare. Use language stdlib `timingSafeEqual` / `compare_digest` / `subtle.ConstantTimeCompare`.
## 🔗 Graph
- 부모: [[Side Channel Attack]] · [[Practical-Cryptography|Cryptography]]
- 변형: [[Spectre]] · [[Cache Timing Attack]] · [[Power Analysis]]
## 🤖 LLM 활용
**언제**: auth code review, HMAC/token compare, custom crypto routines, security audit.
**언제 X**: high-level app logic where no secrets are compared (UI rendering, business rules).
## ❌ 안티패턴
- **`a === b` for secrets**: V8/JIT may early-exit, branch, or short-circuit.
- **Custom "constant-time" without testing**: compiler can re-introduce branches via optimization. Test with `dudect`.
- **Throwing on mismatch**: exception path differs in timing from success path.
- **Logging mismatch position**: leaks comparison index.
- **Comparing hashes in DB**: even hash compare 의 leaks length prefix → use CT.
## 🧪 검증 / 중복
- Verified (Kocher 1996 paper, Node.js crypto docs, Go subtle pkg, OWASP Cryptographic Storage CS).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full canonical (CT compare + branchless + Node/Go/Python + remote attacks) |
@@ -0,0 +1,128 @@
---
id: wiki-2026-0508-v8-javascript-engine
title: V8 JavaScript Engine
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [V8, V8 Engine, Chrome V8]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [v8, javascript, engine, runtime, chrome, node]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: C++
framework: V8
---
# V8 JavaScript Engine
## 매 한 줄
> **"매 Ignition interpreter + TurboFan / Maglev / Sparkplug 의 매 multi-tier JIT 으로 매 JS 를 native 수준에 도달시키는 엔진"**. 매 Chrome / Edge / Node.js / Deno 의 핵심. 매 2026 기준 Maglev (mid-tier JIT) 와 매 pointer compression 으로 매 startup + memory 모두 개선됨.
## 매 핵심
### 매 Multi-tier compilation
- **Ignition** — bytecode interpreter (fastest startup).
- **Sparkplug** — non-optimizing baseline JIT (V8 9.1+).
- **Maglev** — mid-tier optimizing JIT (V8 11.5+, default 11.9+).
- **TurboFan** — top-tier optimizer (slow compile, fast run).
### 매 Hidden classes & inline caches
- 매 JS object 의 매 shape 추적 (V8 internal "Map").
- 매 same-shape access 는 매 monomorphic IC 로 매 native 속도.
- 매 shape 변경 (속성 추가 순서 다름) 매 → polymorphic / megamorphic.
### 매 GC
- Generational: young (scavenger, Minor MC) + old (Mark-Compact, Mark-Sweep).
- Orinoco — concurrent / parallel / incremental.
- Pointer compression (V8 8.0+) — 35-40% heap 절감.
### 매 응용
1. Browser JS — Chrome, Edge.
2. Server JS — Node.js, Deno (V8 기반).
3. Embedded — Electron, CEF.
## 💻 패턴
### Hidden class 친화적 객체
```javascript
// Good — same shape
function User(id, name) { this.id = id; this.name = name; }
// Bad — shape diverges
function User(id, name) { this.id = id; if (name) this.name = name; }
```
### Monomorphic call site
```javascript
function getX(point) { return point.x; }
// pass only one shape consistently → IC stays monomorphic
const points = Array.from({ length: 1e6 }, (_, i) => ({ x: i, y: i }));
points.forEach(getX);
```
### Avoiding deopt
```javascript
function add(a, b) { return a + b; }
add(1, 2); // optimized for number
add('a', 'b'); // deopt — TurboFan must reoptimize
```
### V8 inspector / profiling
```bash
node --inspect-brk app.js
# Chrome devtools → Performance → JS samples
```
### V8 flags for diagnosis
```bash
node --trace-opt --trace-deopt app.js
node --print-bytecode app.js
node --allow-natives-syntax # %OptimizeFunctionOnNextCall
```
### Pointer compression check
```bash
node -p "process.config.variables.v8_enable_pointer_compression"
# 1 → enabled
```
## 매 결정 기준
| 관심사 | Approach |
|---|---|
| Startup 빠름 | Ignition / Sparkplug 충분 |
| Hot loop | Maglev / TurboFan 의 monomorphic 유지 |
| Memory tight | pointer compression + young GC tuning |
| Diagnose perf | `--trace-deopt`, `clinic flame` |
**기본값**: 매 modern V8 default — 매 micro-opt 보다 매 algorithmic 우선.
## 🔗 Graph
- 부모: [[JavaScript]]
- 변형: [[JavaScriptCore]] · [[Hermes]]
- 응용: [[Node.js]] · [[Deno]] · [[Chrome]] · [[Electron]]
- Adjacent: [[JIT Compilation]] · [[Garbage Collection]] · [[Inline Cache]]
## 🤖 LLM 활용
**언제**: 매 perf 분석 가설 세우기, 매 deopt 원인 추정.
**언제 X**: 매 V8 internal 변경 빠름 — 매 매 latest blog 확인 필요.
## ❌ 안티패턴
- **Shape divergence**: 매 conditional property 추가 — 매 polymorphic 유발.
- **delete 연산자**: 매 hidden class transition 강제.
- **`arguments` 변형**: 매 deopt 유발.
- **Try/catch in hot path**: 매 V8 ≤ 7.x 까진 deopt — 매 modern V8 은 ok 이나 매 case-by-case.
## 🧪 검증 / 중복
- Verified (V8 official blog v8.dev, "Maglev" announcement).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Maglev + pointer compression 반영 |
@@ -0,0 +1,141 @@
---
id: wiki-2026-0508-webgl-20
title: WebGL 2.0
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [WebGL 2.0, WebGL2, GLES3 Web]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [webgl, graphics, frontend, gpu, opengl-es]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript / GLSL ES 3.00
framework: WebGL 2.0
---
# WebGL 2.0
## 매 한 줄
> **"매 OpenGL ES 3.0 기반 매 browser 그래픽 API"**. 매 WebGL 1.0 (ES 2.0) 대비 매 transform feedback, 매 UBO, 매 instancing, 매 3D / array textures, 매 GLSL ES 3.00 등 매 modern feature 표준화. 매 2026 기준 매 모든 주요 browser default 지원, 매 WebGPU 으로 매 점진 이행 중이지만 매 fallback baseline.
## 매 핵심
### 매 ES 2.0 → ES 3.0 차이
- **GLSL ES 3.00** — `in`/`out`, layout qualifier, integer texture.
- **VAO** — Vertex Array Object 표준 (1.0 은 extension).
- **UBO** — Uniform Buffer Object (binding point 기반 sharing).
- **Instancing** — `drawArraysInstanced` core (1.0 은 ANGLE_instanced_arrays).
- **Transform feedback** — vertex shader 결과 buffer 로 capture.
- **MRT** — Multiple Render Targets.
- **3D / 2D array textures**, sampler objects.
### 매 응용
1. 매 GPGPU compute (transform feedback).
2. 매 Deferred shading (MRT).
3. 매 Volumetric rendering (3D texture).
## 💻 패턴
### Context 획득
```javascript
const gl = canvas.getContext('webgl2');
if (!gl) throw new Error('WebGL2 unsupported');
```
### VAO + instancing
```javascript
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// setup attribs...
gl.vertexAttribDivisor(instanceAttribLoc, 1); // 1 per instance
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
```
### UBO
```glsl
#version 300 es
layout(std140) uniform Camera {
mat4 view;
mat4 proj;
};
```
```javascript
const ubo = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
gl.bufferData(gl.UNIFORM_BUFFER, 128, gl.DYNAMIC_DRAW);
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, ubo);
const idx = gl.getUniformBlockIndex(program, 'Camera');
gl.uniformBlockBinding(program, idx, 0);
```
### Transform feedback
```javascript
gl.transformFeedbackVaryings(program, ['v_position'], gl.SEPARATE_ATTRIBS);
gl.linkProgram(program);
const tfBuf = gl.createBuffer();
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuf);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, byteLen, gl.DYNAMIC_COPY);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, tfBuf);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, count);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);
```
### MRT
```javascript
gl.drawBuffers([
gl.COLOR_ATTACHMENT0, // diffuse
gl.COLOR_ATTACHMENT1, // normal
gl.COLOR_ATTACHMENT2, // depth/spec
]);
```
### 3D texture
```javascript
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, w, h, d, 0, gl.RED, gl.UNSIGNED_BYTE, data);
```
## 매 결정 기준
| 상황 | API |
|---|---|
| 광범위 호환 / 단순 | WebGL 1 + 매 fallback |
| Modern feature 필요 | WebGL 2 |
| Compute / 매 advanced | WebGPU |
| Library 사용 | Three.js / Babylon.js (auto-detect) |
**기본값**: 매 2026 신규 프로젝트 — WebGPU 가능하면 WebGPU, 아니면 WebGL 2 + WEBGL_multi_draw.
## 🔗 Graph
- 부모: [[WebGL]] · [[OpenGL ES]]
- 변형: [[WebGPU]]
- 응용: [[Three.js]] · [[Babylon.js]] · [[CesiumJS]]
- Adjacent: [[WEBGL_multi_draw]] · [[GLSL]]
## 🤖 LLM 활용
**언제**: 매 boilerplate (VAO/UBO setup), 매 GLSL 변환 (ES 2 → ES 3).
**언제 X**: 매 driver-specific bug — 매 매 platform test 필요.
## ❌ 안티패턴
- **`#version 300 es` 누락**: 매 WebGL2 shader 인데 매 ES 2 syntax.
- **VAO 미사용**: 매 매 draw 마다 attrib 재바인딩 — 매 perf 손실.
- **UBO 정렬 무시**: 매 std140 padding 안 맞춰 매 silent 손상.
- **Loss of context 무시**: 매 GPU reset / 매 tab background — 매 `webglcontextlost` 핸들링 필수.
## 🧪 검증 / 중복
- Verified (Khronos WebGL 2.0 spec, MDN WebGL2RenderingContext).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — VAO/UBO/TF/MRT 패턴 + WebGPU 비교 |
@@ -0,0 +1,176 @@
---
id: wiki-2026-0508-webgl-api
title: WebGL API
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [WebGL, WebGL API, WebGL 1.0, WebGL 2.0]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [webgl, graphics, gpu, gles]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript/GLSL
framework: Web Platform
---
# WebGL API
## 매 한 줄
> **"매 OpenGL ES 의 web — canvas 의 GPU draw"**. WebGL 매 OpenGL ES 2.0 (WebGL1) / ES 3.0 (WebGL2) binding 의 browser. 2026 매 WebGPU rising 매 WebGL 매 still ubiquitous — fallback / wide compatibility 의 사용.
## 매 핵심
### 매 pipeline
- **Vertex shader**: per-vertex transform — clip space.
- **Rasterizer**: triangles → fragments.
- **Fragment shader**: per-pixel color.
- **Framebuffer**: render target — default canvas / FBO offscreen.
### 매 핵심 objects
- **Buffer** (`ARRAY_BUFFER`, `ELEMENT_ARRAY_BUFFER`): vertex / index data.
- **Texture** (2D / cube / 3D-WebGL2 / array-WebGL2).
- **Program**: vertex + fragment shader linked.
- **VAO** (WebGL2): vertex attribute state.
- **UBO** (WebGL2): uniform block — efficient bulk uniform.
- **Framebuffer / Renderbuffer**: offscreen render targets.
### 매 응용
1. **3D libraries**: Three.js / Babylon.js / PlayCanvas.
2. **2D accelerated**: PixiJS / Konva.
3. **Data viz**: deck.gl / Mapbox GL / regl.
4. **Games / interactive**: itch.io HTML5 / Unity WebGL build.
## 💻 패턴
### 매 minimal triangle
```js
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
const vs = `#version 300 es
in vec2 a_pos;
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }`;
const fs = `#version 300 es
precision highp float;
out vec4 fragColor;
void main() { fragColor = vec4(1.0, 0.5, 0.2, 1.0); }`;
function compile(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw gl.getShaderInfoLog(s);
return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vs));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(prog);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]), gl.STATIC_DRAW);
const loc = gl.getAttribLocation(prog, 'a_pos');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(prog);
gl.drawArrays(gl.TRIANGLES, 0, 3);
```
### Texture upload
```js
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.generateMipmap(gl.TEXTURE_2D);
```
### Instanced rendering (WebGL2)
```js
gl.vertexAttribDivisor(instanceLoc, 1); // advance per instance
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 10000); // 10k instances 1 draw call
```
### Uniform Buffer Object (WebGL2)
```js
const ubo = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
gl.bufferData(gl.UNIFORM_BUFFER, mat4Buffer, gl.DYNAMIC_DRAW);
const idx = gl.getUniformBlockIndex(prog, 'Camera');
gl.uniformBlockBinding(prog, idx, 0);
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, ubo);
```
### Framebuffer (offscreen / render-to-texture)
```js
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, w, h, 0, gl.RGBA, gl.HALF_FLOAT, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
// 매 render → tex, then post-process
```
### Transform Feedback (WebGL2 GPU compute-ish)
```js
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, outBuf);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, particleCount);
gl.endTransformFeedback();
```
### Context loss handling
```js
canvas.addEventListener('webglcontextlost', (e) => { e.preventDefault(); });
canvas.addEventListener('webglcontextrestored', () => { rebuildResources(); });
```
## 매 결정 기준
| 상황 | WebGL vs WebGPU |
|---|---|
| 2026 wide compatibility | WebGL2 — Safari iOS / older Android |
| Compute shader | WebGPU |
| Multi-pass complex | WebGPU 매 cleaner state model |
| Existing Three.js / Babylon | WebGL2 fallback + WebGPU primary |
| Simple 2D accel | WebGL2 / 2D canvas |
| AAA-grade graphics | WebGPU |
**기본값**: Three.js with WebGPURenderer + WebGL2 fallback.
## 🔗 Graph
- 부모: [[OpenGL ES]]
- 변형: [[WebGPU]]
- 응용: [[Threejs]] · [[Babylon.js]]
- Adjacent: [[GLSL]] · [[OffscreenCanvas]]
## 🤖 LLM 활용
**언제**: cross-browser 3D / 2D GPU accel / data viz / WebGPU 의 fallback.
**언제 X**: heavy compute (no compute shader in WebGL) / cutting-edge graphics — WebGPU 사용.
## ❌ 안티패턴
- **State thrash**: bind/unbind every draw — batch by program/texture.
- **Sync readback**: `readPixels` 매 GPU stall — use PBO async (WebGL2).
- **No VAO**: re-binding attributes every draw — VAO 매 cache.
- **Uniform per draw call**: UBO 의 bulk update / instancing.
- **Premultiply confusion**: alpha blending 의 incorrect — `UNPACK_PREMULTIPLY_ALPHA_WEBGL`.
## 🧪 검증 / 중복
- Verified (Khronos WebGL 1.0/2.0 spec / MDN WebGL API).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — pipeline, VAO, UBO, FBO, instancing, transform feedback |
@@ -0,0 +1,158 @@
---
id: wiki-2026-0508-wonder
title: Wonder
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Wonder Editor, Wonder.js, Wonder ECS]
duplicate_of: none
source_trust_level: B
confidence_score: 0.75
verification_status: applied
tags: [wonder, ecs, web3d, engine]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript / ReScript
framework: Wonder
---
# Wonder
## 매 한 줄
> **"매 ECS-first web 3D — functional core"**. Wonder 매 ReScript/TypeScript 기반 ECS architecture 3D engine — Wonderland 와 별도의 OSS lineage. 2026 매 niche — Three.js / Babylon / Wonderland Engine 의 dominant 매 main alternative.
## 매 핵심
### 매 design
- **ECS (Entity Component System)**: data-oriented — entity (ID) + component (data) + system (logic).
- **Functional core**: ReScript 의 immutable / pattern matching 의 사용.
- **WebGL2 backend**: GPU draw — WebGPU 매 future direction.
- **Editor**: web-based scene editor — separate from runtime.
### 매 vs Three.js
- **Three.js**: scene-graph imperative OOP — vast ecosystem.
- **Wonder**: ECS data-oriented — 매 cache-friendly bulk update.
- **Tradeoff**: Wonder 매 less plugins / smaller community / steeper learning.
### 매 응용
1. **Data-heavy 3D**: thousands of entities — particle / RTS-like.
2. **Functional codebase**: ReScript shop 의 fit.
3. **Educational**: ECS pattern 의 reference impl.
4. **Custom rendering pipeline**: 직접 control 의 필요한 case.
## 💻 패턴
### ECS basic
```typescript
import { World, defineComponent, defineSystem } from 'wonder-ecs';
const Position = defineComponent({ x: 'f32', y: 'f32', z: 'f32' });
const Velocity = defineComponent({ x: 'f32', y: 'f32', z: 'f32' });
const world = new World();
const entity = world.createEntity();
world.addComponent(entity, Position, { x: 0, y: 0, z: 0 });
world.addComponent(entity, Velocity, { x: 1, y: 0, z: 0 });
const movement = defineSystem([Position, Velocity], (entities, dt) => {
for (const e of entities) {
const p = world.getComponent(e, Position);
const v = world.getComponent(e, Velocity);
p.x += v.x * dt; p.y += v.y * dt; p.z += v.z * dt;
}
});
function tick(dt) { movement(world.query([Position, Velocity]), dt); }
```
### Mesh + render system
```typescript
const Mesh = defineComponent({ geometryId: 'u32', materialId: 'u32' });
const renderSystem = defineSystem([Position, Mesh], (entities) => {
for (const e of entities) {
const p = world.getComponent(e, Position);
const m = world.getComponent(e, Mesh);
renderer.draw(m.geometryId, m.materialId, p);
}
});
```
### SoA storage 매 cache-friendly
```typescript
// 매 Wonder ECS internally 매 SoA — 매 manual 의 example
class PositionSoA {
x: Float32Array; y: Float32Array; z: Float32Array;
constructor(capacity: number) {
this.x = new Float32Array(capacity);
this.y = new Float32Array(capacity);
this.z = new Float32Array(capacity);
}
}
// 매 tight loop 매 cache hit — AoS 보다 2-5x faster
```
### ReScript 매 functional system
```rescript
// 매 ReScript syntax
let movement = (world, dt) => {
let entities = World.query(world, [Position.id, Velocity.id])
entities->Belt.Array.forEach(e => {
let pos = World.getComponent(world, e, Position.id)
let vel = World.getComponent(world, e, Velocity.id)
World.setComponent(world, e, Position.id, {
x: pos.x +. vel.x *. dt,
y: pos.y +. vel.y *. dt,
z: pos.z +. vel.z *. dt,
})
})
}
```
### Asset loading
```typescript
const assets = await Wonder.loadGLTF('scene.gltf');
for (const node of assets.nodes) {
const e = world.createEntity();
world.addComponent(e, Position, node.translation);
world.addComponent(e, Mesh, { geometryId: node.meshId, materialId: node.materialId });
}
```
## 매 결정 기준
| 상황 | Engine choice |
|---|---|
| Mainstream web 3D | Three.js |
| AAA-style + editor | Babylon.js / Wonderland Engine |
| ECS / data-heavy | Wonder / bitECS + Three.js |
| ReScript codebase | Wonder |
| Massive entity count | ECS + InstancedMesh |
**기본값**: Three.js + bitECS — Wonder 매 specific ECS-first / ReScript 의 case.
## 🔗 Graph
- 부모: [[ECS]]
- 변형: [[Threejs]] · [[Babylon.js]] · [[Wonderland Engine]]
- 응용: [[bitECS]]
- Adjacent: [[WebGL API]] · [[WebGPU]]
## 🤖 LLM 활용
**언제**: ECS-first 3D web project / ReScript 사용 codebase / large entity count + custom systems.
**언제 X**: standard 3D scene — Three.js 매 더 ecosystem / artist-friendly editor 필요 — Wonderland / Babylon.
## ❌ 안티패턴
- **OOP scene-graph mindset in ECS**: parent-child mutation 매 anti-ECS — entity hierarchy 의 component 의 모델.
- **Per-entity allocation in tick**: GC pressure — pool / SoA.
- **Wonder + Three.js mix**: rendering 매 conflict — choose one renderer.
## 🧪 검증 / 중복
- Verified (Wonder OSS docs / GitHub).
- 신뢰도 B (smaller community — verify against latest repo).
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — ECS arch, ReScript, SoA, vs Three.js |
@@ -0,0 +1,190 @@
---
id: wiki-2026-0508-wonderland-engine
title: Wonderland Engine
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Wonderland, Wonderland Editor]
duplicate_of: none
source_trust_level: A
confidence_score: 0.85
verification_status: applied
tags: [wonderland, web3d, webxr, engine, vr, ar]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript/TypeScript
framework: Wonderland Engine
---
# Wonderland Engine
## 매 한 줄
> **"매 WebXR-first 3D — Unity-like editor + WebAssembly runtime"**. Wonderland Engine 매 commercial web 3D engine 의 Unity-style scene editor + WASM-compiled C++ runtime — 매 WebXR (Quest browser / Vision Pro Safari) 의 first-class. 2026 매 web VR/AR studio 의 main alternative 의 Three.js + WebXR boilerplate.
## 매 핵심
### 매 architecture
- **C++ runtime → WASM**: 매 native-grade perf — Three.js JS 보다 2-3x throughput.
- **Editor**: native desktop app (Win/Mac/Linux) — scene / prefab / animation.
- **Component system**: TypeScript/JS components — Unity-like MonoBehaviour 패턴.
- **Renderer**: WebGL2 + WebGPU (rolling out) — PBR / shadow / postprocess.
- **WebXR**: built-in VR/AR session + controllers + hand tracking.
### 매 vs alternatives
- **vs Three.js**: editor + perf + WebXR built-in / Three.js 매 더 flexible + ecosystem.
- **vs PlayCanvas**: similar editor model / Wonderland 매 lighter + WebXR-focused.
- **vs Unity WebGL build**: Wonderland 매 way smaller bundle (1-3MB vs 20MB+) + faster load.
- **vs Babylon.js**: 둘 다 mature — Wonderland editor-driven / Babylon API-driven.
### 매 응용
1. **WebXR VR apps**: Quest / Vision Pro — training / education / social.
2. **Mobile AR**: WebXR AR session — product viz / try-on.
3. **Marketing 3D**: brand experience — fast load + editor productivity.
4. **Web games**: lightweight 3D — itch.io / Crazy Games.
## 💻 패턴
### Component (TS) 매 basic
```typescript
import { Component, Property } from '@wonderlandengine/api';
export class Rotator extends Component {
static TypeName = 'rotator';
static Properties = {
speed: Property.float(1.0),
axis: Property.enum(['x', 'y', 'z'], 'y'),
};
speed!: number;
axis!: number;
update(dt: number) {
const rot: [number, number, number] = [0, 0, 0];
rot[this.axis] = this.speed * dt;
this.object.rotateAxisAngleDegLocal(
[this.axis === 0 ? 1 : 0, this.axis === 1 ? 1 : 0, this.axis === 2 ? 1 : 0],
this.speed * dt
);
}
}
```
### WebXR session
```typescript
import { Component, Property } from '@wonderlandengine/api';
import { CursorTarget } from '@wonderlandengine/components';
export class VRController extends Component {
static TypeName = 'vr-controller';
start() {
this.engine.onXRSessionStart.add((session) => {
console.log('XR session started:', session.mode);
});
}
onActivate() {
this.engine.requestXRSession('immersive-vr', ['local-floor', 'hand-tracking']);
}
}
```
### Hand tracking
```typescript
import { HandTracking } from '@wonderlandengine/components';
// 매 editor 의 component attach — wrist / fingertip joint 의 transform 매 tracked
// runtime 매 joint position / pinch gesture 의 query
```
### Physics (Rapier integration)
```typescript
import { Component } from '@wonderlandengine/api';
import { PhysXComponent } from '@wonderlandengine/components';
export class Spawner extends Component {
static TypeName = 'spawner';
spawn() {
const obj = this.engine.scene.addObject(this.object);
obj.addComponent('mesh', { mesh: this.cubeMesh, material: this.cubeMat });
obj.addComponent('physx', {
shape: 'Box',
mass: 1.0,
extents: [0.1, 0.1, 0.1],
});
}
}
```
### Mesh streaming + dynamic load
```typescript
import { loadGLTF } from '@wonderlandengine/api';
async function loadAsset(url: string) {
const { meshes, materials } = await loadGLTF(this.engine, url);
const obj = this.engine.scene.addObject();
obj.addComponent('mesh', { mesh: meshes[0], material: materials[0] });
return obj;
}
```
### Animation player
```typescript
import { AnimationComponent } from '@wonderlandengine/api';
const anim = this.object.getComponent('animation') as AnimationComponent;
anim.play();
anim.speed = 0.5;
anim.onEvent.add((evt) => console.log('anim event', evt));
```
### Networked multiplayer (Wonderland Cloud / custom)
```typescript
// 매 Wonderland Cloud — managed multiplayer
import { CloudClient } from '@wonderlandengine/cloud';
const client = new CloudClient({ projectId: 'abc123' });
await client.connect();
client.onPlayerJoin.add((p) => spawnAvatar(p));
client.sendState({ pos: this.object.getPositionWorld() });
```
## 매 결정 기준
| 상황 | Engine |
|---|---|
| WebXR VR/AR primary | Wonderland Engine |
| 2D UI heavy | Three.js / Babylon (DOM/CSS overlay 매 easier) |
| Editor-driven workflow | Wonderland / PlayCanvas |
| Code-first, max flexibility | Three.js |
| Smallest bundle | Wonderland (WASM-optimized) |
| Existing Unity team transitioning | Wonderland (familiar component model) |
**기본값**: WebXR target 매 Wonderland / general 3D web 매 Three.js.
## 🔗 Graph
- 부모: [[WebXR]]
- 변형: [[Threejs]] · [[Babylon.js]] · [[Wonder]]
- Adjacent: [[WebAssembly]] · [[WebGL API]] · [[WebGPU]]
## 🤖 LLM 활용
**언제**: WebXR-first project / Unity team 의 web migration / lightweight 3D + editor productivity.
**언제 X**: code-first / massive Three.js ecosystem dependency / commercial license 매 issue (free tier limits).
## ❌ 안티패턴
- **Editor scene 의 ignore**: runtime-only entity creation — editor workflow 매 lost.
- **Heavy per-frame allocation**: TypedArray reuse / vec3 pool 의 사용.
- **Mixing Three.js loaders**: glTF 의 Wonderland loader 의 사용 — material / animation binding.
- **No LOD on mobile XR**: Quest 매 vertex / fragment budget 의 strict — LOD + texture compress.
## 🧪 검증 / 중복
- Verified (Wonderland Engine official docs / GitHub).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — architecture, components, WebXR, physics, hand tracking |
@@ -0,0 +1,234 @@
---
id: wiki-2026-0508-zero-runtime-css-in-js
title: Zero-Runtime CSS-in-JS
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Zero-Runtime CSS-in-JS, Zero Runtime CSS, Static CSS-in-JS]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [css, css-in-js, build-time, zero-runtime, vanilla-extract, linaria]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: vanilla-extract
---
# Zero-Runtime CSS-in-JS
## 매 한 줄
> **"매 build time 에 CSS 로 추출 — runtime overhead 0"**. 매 styled-components/emotion 의 runtime injection 의 단점 (FCP 손실, hydration cost, RSC 비호환) 매 해결. 매 vanilla-extract (Mark Dalgleish), Linaria, Panda CSS, StyleX (Meta) 매 대표주자. 매 React Server Component 시대의 사실상 표준.
## 매 핵심
### 매 runtime CSS-in-JS 의 문제
- **Bundle size**: 매 emotion ~10KB, styled-components ~12KB minified.
- **FCP 손실**: 매 JS 가 parse → execute → CSS inject → paint. 매 3-tier waterfall.
- **Hydration cost**: 매 server-rendered CSS 와 client style 의 sync overhead.
- **RSC 비호환**: 매 React Server Component 매 runtime context 없음 — 매 styled-components/emotion 매 client-only.
- **Re-render cost**: 매 dynamic style prop 의 매 render 마다 hash + inject.
### 매 zero-runtime 의 해결
- **Build-time extraction**: 매 .css.ts → .css 로 매 extract.
- **Type safety**: 매 TypeScript 로 token, theme, variants 매 type-check.
- **No runtime**: 매 0 KB 추가 — 매 static CSS 와 동일.
- **RSC 호환**: 매 build-time output 이라 server/client 무관.
- **CSS-only features**: 매 :hover, :focus, media query 매 CSS 자체 사용.
### 매 응용
1. Next.js App Router (RSC) 와 매 vanilla-extract pairing.
2. Design system 의 token-based theming (light/dark, brand variant).
3. Component library publishing (no runtime dependency).
4. Performance-critical apps (e-commerce, content sites).
## 💻 패턴
### vanilla-extract — basic style
```ts
// button.css.ts
import { style } from '@vanilla-extract/css';
export const button = style({
padding: '8px 16px',
borderRadius: 4,
background: 'blue',
color: 'white',
':hover': {
background: 'darkblue',
},
});
// button.tsx
import { button } from './button.css';
export const Button = (props) => <button className={button} {...props} />;
```
### vanilla-extract — variants (recipes)
```ts
// button.css.ts
import { recipe } from '@vanilla-extract/recipes';
export const button = recipe({
base: { padding: 8, borderRadius: 4 },
variants: {
intent: {
primary: { background: 'blue', color: 'white' },
danger: { background: 'red', color: 'white' },
},
size: {
sm: { fontSize: 12 },
md: { fontSize: 14 },
lg: { fontSize: 18 },
},
},
defaultVariants: { intent: 'primary', size: 'md' },
});
// usage
<button className={button({ intent: 'danger', size: 'lg' })}>Delete</button>
```
### vanilla-extract — theme contract
```ts
// theme.css.ts
import { createTheme, createThemeContract } from '@vanilla-extract/css';
export const vars = createThemeContract({
color: { brand: null, text: null, bg: null },
space: { sm: null, md: null, lg: null },
});
export const lightTheme = createTheme(vars, {
color: { brand: '#0066ff', text: '#000', bg: '#fff' },
space: { sm: '4px', md: '8px', lg: '16px' },
});
export const darkTheme = createTheme(vars, {
color: { brand: '#3399ff', text: '#fff', bg: '#000' },
space: { sm: '4px', md: '8px', lg: '16px' },
});
// component uses vars
import { vars } from './theme.css';
const card = style({
background: vars.color.bg,
color: vars.color.text,
padding: vars.space.md,
});
```
### Linaria — tagged template
```ts
import { css } from '@linaria/core';
import { styled } from '@linaria/react';
const button = css`
padding: 8px 16px;
background: ${({ primary }) => primary ? 'blue' : 'gray'};
`;
const Button = styled.button`
border-radius: 4px;
&:hover { opacity: 0.8; }
`;
// 매 build-time 에 CSS 추출, dynamic prop 매 CSS variable 로 변환.
```
### Panda CSS — atomic + tokens
```ts
// panda.config.ts
export default defineConfig({
theme: {
tokens: {
colors: { brand: { value: '#0066ff' } },
},
},
});
// component
import { css } from 'styled-system/css';
<div className={css({
bg: 'brand',
p: '4',
rounded: 'md',
_hover: { opacity: 0.8 },
})}>
Hello
</div>
// 매 build-time 에 atomic class generation. Tailwind-like.
```
### StyleX (Meta) — atomic
```ts
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
button: {
padding: '8px 16px',
backgroundColor: { default: 'blue', ':hover': 'darkblue' },
},
});
<button {...stylex.props(styles.button)}>Click</button>
// 매 Meta Facebook/Instagram 사용. 매 atomic class deduplication.
```
### Migration — emotion → vanilla-extract
```ts
// Before (emotion)
const Button = styled.button`
padding: ${props => props.size === 'lg' ? '16px' : '8px'};
`;
// After (vanilla-extract recipes)
const button = recipe({
base: {},
variants: {
size: { sm: { padding: 8 }, lg: { padding: 16 } },
},
});
const Button = ({ size, ...rest }) => <button className={button({ size })} {...rest} />;
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Next.js App Router (RSC) | vanilla-extract or Panda CSS. |
| TypeScript theme system | vanilla-extract (createThemeContract). |
| Tailwind-style atomic | Panda CSS or StyleX. |
| Existing styled-components codebase | Linaria (similar API, build-time). |
| Component library 배포 | vanilla-extract (zero runtime dep). |
| Quick prototype | Tailwind (no build setup) or emotion. |
**기본값**: vanilla-extract (Next.js + RSC), Panda CSS (atomic), Linaria (migration).
## 🔗 Graph
- 부모: [[CSS_Architecture_and_Styling|CSS-in-JS]]
- 변형: [[vanilla-extract]] · [[Panda CSS]]
- 응용: [[Design System]] · [[React Server Components]]
- Adjacent: [[CSS_Architecture_and_Styling|Tailwind CSS]] · [[CSS Modules]] · [[Styled_Components|styled-components]]
## 🤖 LLM 활용
**언제**: Next.js App Router (RSC) 의, performance-critical site, design system 의 type-safe theming, component library 배포 의.
**언제 X**: 매 highly dynamic runtime style (theme switcher 매 CSS var 로 가능 — 그 외 의), 매 prototype 단계의 (Tailwind 매 빠름).
## ❌ 안티패턴
- **Runtime variable 을 build-time 으로 expect**: 매 `style({ color: dynamicVar })``dynamicVar` 매 build time 의 evaluable 해야. Runtime value 매 CSS variable 로.
- **emotion + RSC 혼합**: 매 RSC 에서 emotion `css` prop X. 매 Server component 매 zero-runtime 만.
- **css.ts 의 conditional logic**: 매 `if (process.env.NODE_ENV)` 매 build-time 에 evaluated — 매 runtime 변경 X.
- **Import side effect 망각**: 매 vanilla-extract `*.css.ts` import 가 CSS 추출 trigger — tree-shake 의 영향.
- **Tailwind 와 zero-runtime 의 비교 오류**: 매 Tailwind 도 zero-runtime — 단지 매 CSS-in-JS 가 아닌 utility-first.
## 🧪 검증 / 중복
- Verified (vanilla-extract docs, Linaria docs, Panda CSS docs, StyleX official).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — vanilla-extract, Linaria, Panda CSS, StyleX patterns + RSC compatibility 추가 |
@@ -0,0 +1,224 @@
---
id: wiki-2026-0508-starttransition
title: startTransition
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [startTransition, useTransition, React transition]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [react, concurrent, startTransition, useTransition, priority]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react-19
---
# startTransition
## 매 한 줄
> **"매 React 18+ 의 update priority API — non-urgent state update 매 mark"**. 매 input typing (urgent) 와 search result render (non-urgent) 의 분리. 매 stale UI 표시, interruptible re-render, Suspense fallback skip 매 가능. 매 useDeferredValue 와 함께 React Concurrent Mode 의 핵심.
## 매 핵심
### 매 priority 분류
- **Urgent (synchronous)**: 매 input value, click 의 visual feedback. 매 default.
- **Transition (low-priority)**: 매 expensive list filter, route change, data 의 UI update.
- 매 urgent update 가 transition 중 들어오면 매 transition interrupt + restart.
### 매 useTransition vs startTransition
- `useTransition()`: 매 component 안 — `[isPending, startTransition]` 반환. 매 pending UI 표시 가능.
- `startTransition(fn)`: 매 import 직접 — pending state 추적 불필요한 case.
### 매 동작
- 매 transition 안 setState 매 low-priority 로 mark.
- 매 React 매 다른 urgent work 우선 처리, 매 transition 매 idle 시 처리.
- 매 concurrent renderer 매 stale tree 유지하면서 new tree 백그라운드 build.
- 매 Suspense — 매 transition 의 fallback 표시 skip (existing UI 유지).
### 매 응용
1. Search input — typing (urgent) + result list (transition).
2. Tab switch — instant feedback + heavy content (transition).
3. Route change — 매 fallback skip 으로 smooth navigation.
4. Filter / sort — 매 list 가 매우 클 때.
5. Data fetch + render — 매 Suspense + transition 조합.
## 💻 패턴
### useTransition — search
```tsx
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Item[]>([]);
const [isPending, startTransition] = useTransition();
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value); // 매 urgent — input 즉시 update
startTransition(() => {
setResults(filterItems(e.target.value)); // 매 low-priority
});
};
return (
<>
<input value={query} onChange={onChange} />
{isPending && <Spinner />}
<ResultList items={results} />
</>
);
}
```
### Tab switch — instant + heavy content
```tsx
function Tabs() {
const [tab, setTab] = useState<'home' | 'reports' | 'settings'>('home');
const [isPending, startTransition] = useTransition();
const select = (next: typeof tab) => {
startTransition(() => setTab(next));
};
return (
<>
<TabButton onClick={() => select('home')} active={tab === 'home'}>Home</TabButton>
<TabButton onClick={() => select('reports')} active={tab === 'reports'}>Reports</TabButton>
{isPending && <span>Loading...</span>}
{tab === 'home' && <HomeContent />}
{tab === 'reports' && <ReportsContent />}
</>
);
}
// 매 click 의 visual feedback 즉시, content 매 transition.
```
### useDeferredValue — alternative
```tsx
function Search({ query }: { query: string }) {
const deferred = useDeferredValue(query);
const isStale = deferred !== query;
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<ResultList query={deferred} />
</div>
);
}
// 매 useDeferredValue: 매 prop / state 에서 derive — startTransition 보다 declarative.
```
### Suspense — fallback skip during transition
```tsx
function App() {
const [page, setPage] = useState(0);
const [isPending, startTransition] = useTransition();
return (
<Suspense fallback={<Skeleton />}>
<button onClick={() => startTransition(() => setPage(p => p + 1))}>
Next
</button>
{isPending && <Spinner />}
<PageData page={page} /> {/* 매 suspends on data fetch */}
</Suspense>
);
}
// 매 transition 의 page change 시 매 Skeleton X — 기존 page UI 유지 + spinner.
```
### React 19 — useTransition 의 async support
```tsx
function SaveButton() {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const save = () => {
startTransition(async () => {
// 매 React 19 부터 async function 직접 지원
try {
await api.save(formData);
} catch (e) {
setError(String(e));
}
});
};
return (
<button onClick={save} disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
);
}
// 매 React 18 매 async 직접 X — startTransition 의 outer wrap 필요.
```
### Route change — Next.js App Router
```tsx
'use client';
import { useRouter } from 'next/navigation';
import { useTransition } from 'react';
function Nav() {
const router = useRouter();
const [isPending, startTransition] = useTransition();
return (
<button onClick={() => startTransition(() => router.push('/dashboard'))}>
{isPending ? 'Loading...' : 'Dashboard'}
</button>
);
}
```
### Combined with flushSync (반대 우선순위)
```tsx
// 매 startTransition: 매 low-priority, batchable, interruptible.
startTransition(() => setBigList(newData));
// 매 flushSync: 매 high-priority, sync, immediate.
flushSync(() => setUrgent(true));
// 매 둘은 정반대 — 매 같이 사용 X.
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Heavy filter / sort with input | useTransition. |
| Tab / route switch | useTransition. |
| Derived value from prop | useDeferredValue. |
| Async save / submit (React 19+) | useTransition (async). |
| Imperative DOM after state | flushSync (반대). |
| Simple fast state | 매 plain setState. |
**기본값**: 매 user input 분리 의 concurrent UI — useTransition. 매 prop-derived expensive 은 useDeferredValue.
## 🔗 Graph
- 부모: [[React]] · [[Concurrent React]]
- 변형: [[useTransition]] · [[useDeferredValue]] · [[Suspense]]
- 응용: [[Search]]
- Adjacent: [[flushSync]] · [[Automatic Batching]] · [[React Server Components]]
## 🤖 LLM 활용
**언제**: 매 input + heavy filter, tab switch, route change with data fetch, async form submit (React 19+) 의.
**언제 X**: 매 simple light state update (overhead 무의미), 매 imperative DOM 의 즉시 sync (flushSync), 매 server-only logic.
## ❌ 안티패턴
- **모든 setState 에 startTransition**: 매 useless overhead, 매 input lag 등 의 unintended effect.
- **isPending 무시**: 매 visual feedback 없으면 user confusion.
- **External state (zustand etc.) 의 transition**: 매 useTransition 매 React state 만 affect — external store 매 자체 batching.
- **Animation 의 transition**: 매 animation 매 urgent — interrupted 되면 jank. flushSync / RAF 사용.
- **React 18 의 async startTransition**: 매 React 18 매 sync function 만 — async 매 outer wrap 또는 React 19 upgrade.
## 🧪 검증 / 중복
- Verified (React docs useTransition, React 18 release notes, React 19 changes).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — startTransition / useTransition patterns, useDeferredValue 비교, React 19 async 추가 |
@@ -0,0 +1,292 @@
---
id: wiki-2026-0508-vanilla-extract
title: vanilla-extract
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [vanilla-extract, vanilla extract, ve, css.ts]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [css-in-js, zero-runtime, vanilla-extract, typescript, build-time]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: vanilla-extract
---
# vanilla-extract
## 매 한 줄
> **"매 TypeScript 로 type-safe 한 zero-runtime CSS-in-JS"**. 매 Mark Dalgleish (Seek) 가 2021 년 출시. 매 `.css.ts` 파일을 build time 에 static CSS 로 추출, 매 token / variant / theme 매 모두 typed. 매 Next.js App Router (RSC) 의 사실상 표준, 매 component library 배포 의 zero-runtime guarantee.
## 매 핵심
### 매 핵심 API
- `style({...})`: 매 single CSS class 생성 → unique class name 반환.
- `globalStyle('selector', {...})`: 매 global selector style.
- `recipe({base, variants})`: 매 variant-based component (CVA-like, type-safe).
- `createTheme(contract, values)`: 매 token 의 ThemeProvider 대안.
- `createThemeContract({...})`: 매 token shape 정의 (multi-theme).
- `createVar()`: 매 CSS custom property (runtime dynamic).
- `style([base, override])`: 매 style composition.
### 매 build setup
- **Webpack**: `@vanilla-extract/webpack-plugin`.
- **Vite**: `@vanilla-extract/vite-plugin`.
- **Next.js**: `@vanilla-extract/next-plugin` (App Router 호환).
- **esbuild / Rollup / Parcel**: 매 dedicated plugins.
- 매 결과: `.css.ts``.css` 추출 + `.js` 의 class name string export.
### 매 응용
1. Design system 의 token-based theming.
2. Next.js App Router 의 RSC-compatible styling.
3. Component library 의 zero-runtime publish.
4. CVA-style variants via recipes.
5. CSS variable scoped to component.
## 💻 패턴
### Basic style
```ts
// button.css.ts
import { style } from '@vanilla-extract/css';
export const button = style({
padding: '8px 16px',
borderRadius: 4,
background: 'blue',
color: 'white',
cursor: 'pointer',
selectors: {
'&:hover': { background: 'darkblue' },
'&:disabled': { opacity: 0.5, cursor: 'not-allowed' },
},
});
// button.tsx
import { button } from './button.css';
export const Button = (props) => <button className={button} {...props} />;
```
### Theme contract — multi-theme
```ts
// theme.css.ts
import { createTheme, createThemeContract } from '@vanilla-extract/css';
export const vars = createThemeContract({
color: { brand: null, text: null, bg: null, border: null },
space: { xs: null, sm: null, md: null, lg: null, xl: null },
radius: { sm: null, md: null, lg: null },
font: { sans: null, mono: null },
});
export const lightTheme = createTheme(vars, {
color: { brand: '#0066ff', text: '#111', bg: '#fff', border: '#e0e0e0' },
space: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px' },
radius: { sm: '2px', md: '4px', lg: '8px' },
font: { sans: 'Inter, sans-serif', mono: 'JetBrains Mono, monospace' },
});
export const darkTheme = createTheme(vars, {
color: { brand: '#3399ff', text: '#fff', bg: '#0a0a0a', border: '#222' },
space: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px' },
radius: { sm: '2px', md: '4px', lg: '8px' },
font: { sans: 'Inter, sans-serif', mono: 'JetBrains Mono, monospace' },
});
// app.tsx
<html className={isDark ? darkTheme : lightTheme}>
```
### Component using vars
```ts
// card.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme.css';
export const card = style({
background: vars.color.bg,
color: vars.color.text,
border: `1px solid ${vars.color.border}`,
padding: vars.space.md,
borderRadius: vars.radius.lg,
fontFamily: vars.font.sans,
});
```
### Recipes — variants (CVA-like)
```ts
// button.css.ts
import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
import { vars } from './theme.css';
export const button = recipe({
base: {
padding: vars.space.sm,
borderRadius: vars.radius.md,
cursor: 'pointer',
border: 'none',
fontFamily: vars.font.sans,
},
variants: {
intent: {
primary: { background: vars.color.brand, color: '#fff' },
secondary: { background: 'transparent', color: vars.color.brand,
border: `1px solid ${vars.color.brand}` },
danger: { background: '#dc2626', color: '#fff' },
},
size: {
sm: { fontSize: 12, padding: vars.space.xs },
md: { fontSize: 14, padding: vars.space.sm },
lg: { fontSize: 18, padding: vars.space.md },
},
fullWidth: {
true: { width: '100%' },
},
},
compoundVariants: [
{ variants: { intent: 'primary', size: 'lg' },
style: { fontWeight: 'bold' } },
],
defaultVariants: { intent: 'primary', size: 'md' },
});
export type ButtonVariants = RecipeVariants<typeof button>;
// type ButtonVariants = { intent?: 'primary' | 'secondary' | 'danger'; size?: ...; fullWidth?: boolean }
// usage
<button className={button({ intent: 'danger', size: 'lg' })}>Delete</button>
```
### Sprinkles — atomic CSS (Tailwind-like)
```ts
// sprinkles.css.ts
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
const space = defineProperties({
properties: {
padding: { sm: 8, md: 16, lg: 24 },
margin: { sm: 8, md: 16, lg: 24 },
},
shorthands: { p: ['padding'], m: ['margin'] },
});
export const sprinkles = createSprinkles(space);
// usage
<div className={sprinkles({ p: 'md', m: 'sm' })}>...</div>
```
### Global style + reset
```ts
// reset.css.ts
import { globalStyle } from '@vanilla-extract/css';
globalStyle('html, body', {
margin: 0,
padding: 0,
fontFamily: 'system-ui, sans-serif',
});
globalStyle('*', {
boxSizing: 'border-box',
});
// 매 import 'one' 으로 reset 적용
// app.tsx: import './reset.css';
```
### Dynamic value via createVar
```ts
// progress.css.ts
import { createVar, style } from '@vanilla-extract/css';
export const progressVar = createVar();
export const bar = style({
width: progressVar,
background: 'blue',
height: 4,
transition: 'width 0.3s',
});
// progress.tsx
import { assignInlineVars } from '@vanilla-extract/dynamic';
<div
className={bar}
style={assignInlineVars({ [progressVar]: `${percent}%` })}
/>
// 매 runtime dynamic — CSS variable 로 변환.
```
### Style composition
```ts
import { style } from '@vanilla-extract/css';
const base = style({ padding: 8, fontSize: 14 });
const primary = style([base, { background: 'blue', color: 'white' }]);
// 매 primary class = base class + extra rules.
```
### Next.js App Router setup
```ts
// next.config.js
const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin');
const withVanillaExtract = createVanillaExtractPlugin();
module.exports = withVanillaExtract({
// ... existing config
});
// app/layout.tsx
import { lightTheme } from './theme.css';
import './reset.css';
export default function RootLayout({ children }) {
return <html className={lightTheme}>{children}</html>;
}
// 매 RSC 호환 — server component 에서 매 className 사용 가능.
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Next.js App Router (RSC) | vanilla-extract (built-in plugin). |
| Type-safe theme tokens | `createThemeContract` + `createTheme`. |
| CVA-style variants | `recipe`. |
| Atomic / Tailwind-like | `sprinkles`. |
| Runtime dynamic value | `createVar` + `assignInlineVars`. |
| Component library 배포 | vanilla-extract (zero runtime). |
**기본값**: `style` (single class), `recipe` (variants), `createThemeContract` (multi-theme), `createVar` (runtime dynamic).
## 🔗 Graph
- 부모: [[CSS_Architecture_and_Styling|CSS-in-JS]] · [[Zero-Runtime CSS-in-JS]]
- 변형: [[Panda CSS]] · [[CSS Modules]]
- 응용: [[Design System]] · [[React Server Components]]
- Adjacent: [[CVA]] · [[CSS_Architecture_and_Styling|Tailwind CSS]]
## 🤖 LLM 활용
**언제**: Next.js App Router (RSC), type-safe design system, component library 배포, multi-theme (light/dark/brand) 의.
**언제 X**: 매 simple prototype (Tailwind 매 빠름), 매 highly-dynamic per-pixel value (CSS-in-JS runtime 가 더 적합), 매 already emotion / styled-components codebase 의 incremental migration 어려움.
## ❌ 안티패턴
- **Runtime value 직접 사용**: 매 `style({ width: dynamicWidth })``dynamicWidth` 매 build time 에 evaluable 해야. 매 runtime 매 `createVar`.
- **`.css.ts` 의 conditional logic**: 매 `if (env.X)` 매 build-time evaluated — 매 runtime 분기 X.
- **Theme class 의 dynamic apply 미숙**: 매 theme 변경 시 `<html className={isDark ? darkTheme : lightTheme}>` — 매 root 단위.
- **plugin setup 누락**: 매 webpack/vite plugin 없으면 .css.ts 매 그냥 TS file 로 처리 — error.
- **`recipe` 의 base 의 selector**: 매 `recipe` base 안의 `selectors` 매 매 supported 하나 매 syntax 주의.
## 🧪 검증 / 중복
- Verified (vanilla-extract.style 공식 문서, GitHub seek-oss/vanilla-extract).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — vanilla-extract API 전체 (style, recipe, theme contract, sprinkles, createVar) 추가 |
@@ -0,0 +1,247 @@
---
id: wiki-2026-0508-다크-모드-및-다중-브랜드-테마-동적-전환-시스템
title: 다크 모드 및 다중 브랜드 테마 동적 전환 시스템
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Dark Mode, Theme Switching, Multi-brand Theming]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, css, theming, dark-mode, design-system]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript/CSS
framework: React/Next.js
---
# 다크 모드 및 다중 브랜드 테마 동적 전환 시스템
## 매 한 줄
> **"매 CSS variables × design tokens × runtime swap 의 zero-flash theming"**. 매 다크 모드는 단순 색상 toggle이 아닌, design token system의 multi-channel orchestration. 2026 표준은 `light-dark()` CSS function + `prefers-color-scheme` + system token + brand override 의 4-layer cascade.
## 매 핵심
### 매 4-Layer Token Architecture
1. **Primitive tokens**: `--color-blue-500: oklch(60% 0.2 240)`.
2. **Semantic tokens**: `--color-bg-primary` (theme-aware).
3. **Component tokens**: `--button-bg = --color-action`.
4. **Brand override**: `[data-brand="acme"] { --color-action: ... }`.
### 매 SSR Flash 방지
- Inline blocking script (head) 의 `localStorage` read → `<html data-theme="dark">` 의 set before paint.
- Next.js: `next-themes` library 의 표준.
- Cookie-based 의 SSR-aware (Next 15 RSC).
### 매 응용
1. GitHub 의 `light/light-high-contrast/dark/dark-dimmed/dark-high-contrast` 의 5 modes.
2. Stripe Dashboard 의 brand switcher (Atlas, Climate 등).
3. Linear 의 theme + accent color picker.
4. VS Code 의 theme marketplace (color-theme JSON).
## 💻 패턴
### 1. CSS `light-dark()` (2026 baseline)
```css
:root {
color-scheme: light dark;
--bg: light-dark(white, #0a0a0a);
--fg: light-dark(#0a0a0a, white);
--accent: light-dark(oklch(55% 0.2 250), oklch(70% 0.18 250));
}
[data-theme="light"] { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }
```
### 2. Multi-brand token override
```css
:root[data-brand="default"] { --brand-primary: oklch(55% 0.2 250); }
:root[data-brand="acme"] { --brand-primary: oklch(60% 0.22 30); }
:root[data-brand="globex"] { --brand-primary: oklch(58% 0.25 140); }
.btn-primary {
background: var(--brand-primary);
color: light-dark(white, black);
}
```
### 3. No-flash inline script (Next.js layout.tsx)
```tsx
const themeScript = `
(function() {
try {
const saved = localStorage.getItem('theme');
const sys = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const theme = saved || sys;
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
} catch (e) {}
})();
`;
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
</head>
<body>{children}</body>
</html>
);
}
```
### 4. React Theme Provider (next-themes 스타일)
```tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
type Theme = 'light' | 'dark' | 'system';
const ThemeCtx = createContext<{ theme: Theme; setTheme: (t: Theme) => void }>(null!);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>('system');
useEffect(() => {
const saved = (localStorage.getItem('theme') as Theme) || 'system';
setThemeState(saved);
}, []);
useEffect(() => {
const resolved = theme === 'system'
? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: theme;
document.documentElement.dataset.theme = resolved;
document.documentElement.style.colorScheme = resolved;
}, [theme]);
const setTheme = (t: Theme) => {
localStorage.setItem('theme', t);
setThemeState(t);
};
return <ThemeCtx.Provider value={{ theme, setTheme }}>{children}</ThemeCtx.Provider>;
}
export const useTheme = () => useContext(ThemeCtx);
```
### 5. Brand context + dynamic token injection
```tsx
'use client';
import { useEffect } from 'react';
export function BrandProvider({ brand, children }: { brand: 'acme' | 'globex'; children: React.ReactNode }) {
useEffect(() => {
document.documentElement.dataset.brand = brand;
}, [brand]);
return <>{children}</>;
}
```
### 6. Tailwind v4 + design tokens
```css
/* app/globals.css */
@import "tailwindcss";
@theme {
--color-bg: light-dark(white, #0a0a0a);
--color-fg: light-dark(#0a0a0a, white);
--color-brand: var(--brand-primary, oklch(55% 0.2 250));
}
```
```tsx
<button className="bg-brand text-bg">Click</button>
```
### 7. Image / asset variants (theme-aware)
```tsx
import Image from 'next/image';
import { useTheme } from '@/lib/theme';
export function Logo() {
const { theme } = useTheme();
return (
<picture>
<source srcSet="/logo-dark.svg" media="(prefers-color-scheme: dark)" />
<img src="/logo-light.svg" alt="Logo" />
</picture>
);
}
```
### 8. View Transitions API (smooth theme swap)
```ts
async function toggleTheme(next: 'light' | 'dark') {
if (!document.startViewTransition) {
document.documentElement.dataset.theme = next;
return;
}
await document.startViewTransition(() => {
document.documentElement.dataset.theme = next;
}).ready;
}
```
```css
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 200ms;
}
```
### 9. SSR cookie-based theme (Next 15 RSC)
```tsx
// app/layout.tsx (server)
import { cookies } from 'next/headers';
export default async function Layout({ children }) {
const theme = (await cookies()).get('theme')?.value ?? 'light';
return <html data-theme={theme}>{children}</html>;
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| 단일 브랜드 + dark mode | CSS `light-dark()` + `prefers-color-scheme` |
| 다중 브랜드 SaaS | `data-brand` attribute + token override |
| SSR Next.js | next-themes OR cookie-based RSC |
| Tailwind v4 | `@theme` + CSS vars |
| Smooth transition | View Transitions API |
| Legacy browser | Class-based (`.dark`) + localStorage |
**기본값**: CSS `light-dark()` + `data-theme` + `data-brand` + cookie SSR + next-themes.
## 🔗 Graph
- 부모: [[Design_Systems]] · [[CSS_Variables]]
- 변형: [[Design_Tokens]]
- 응용: [[Tailwind_v4]] · [[Styled_Components_v6]] · [[CSS_Modules]]
- Adjacent: [[View_Transitions_API]]
## 🤖 LLM 활용
**언제**: token naming convention 제안, contrast ratio audit, brand palette 생성 (OKLCH 기반).
**언제 X**: 최종 brand color decision (디자이너), accessibility AA/AAA 의 자동 보장은 X.
## ❌ 안티패턴
- **No-flash 미구현**: hydration 후 theme 적용 → FOUC.
- **Hardcoded hex**: design token 의 bypass — multi-brand 의 X.
- **JS-only theming**: CSS-only 의 fallback 없으면 SSR flash.
- **Inverted contrast**: 다크 모드의 단순 색상 invert — semantic hierarchy 의 break.
- **`!important` overuse**: token cascade 의 collapse.
- **`localStorage` SSR access**: hydration mismatch — `useEffect`로 lazy.
## 🧪 검증 / 중복
- Verified (web.dev color-scheme guide, MDN `light-dark()`, next-themes docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — dark mode + multi-brand theming full content |
@@ -0,0 +1,169 @@
---
id: wiki-2026-0508-단일-진실-공급원-single-source-of-truth
title: 단일 진실 공급원(Single Source of Truth)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [SSOT, Single Source of Truth, 진실의 단일 공급원]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [architecture, state-management, ssot, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react-19
---
# 단일 진실 공급원(Single Source of Truth)
## 매 한 줄
> **"매 모든 derived state 의 single canonical origin"**. 매 1970s database normalization 에서 시작 — 매 modern frontend (Redux, TanStack Query, RSC) 의 핵심 원리. 매 duplication 의 elimination → 매 inconsistency bug 의 소멸.
## 매 핵심
### 매 정의
- 매 한 데이터 의 **하나의** authoritative location.
- 매 다른 모든 view/component 의 derive (read-only projection).
- 매 update 의 single entry point — 매 race / drift 의 prevention.
### 매 frontend 적용 layer
- **Server state SSOT**: 매 backend DB — 매 client 의 cache (TanStack Query, SWR).
- **URL state SSOT**: 매 query params / path — 매 shareable, refreshable.
- **Form state SSOT**: 매 RHF / Formik 의 form object — 매 controlled input 의 derive.
- **Global UI state SSOT**: 매 Zustand / Jotai store — 매 cross-component shared.
### 매 응용
1. TanStack Query — server SSOT mirror.
2. RSC (React Server Components) — server 의 SSOT 그대로 stream.
3. URL-driven state (nuqs, TanStack Router) — 매 shareable SSOT.
4. CRDT (Yjs, Automerge) — 매 distributed SSOT.
## 💻 패턴
### Server SSOT via TanStack Query
```typescript
// ❌ duplication — local state mirrors server
const [user, setUser] = useState<User | null>(null);
useEffect(() => { fetchUser().then(setUser); }, []);
// ✅ SSOT — server is truth, query is cached projection
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
```
### URL as SSOT (nuqs)
```typescript
import { useQueryState, parseAsInteger } from 'nuqs';
function ProductList() {
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
const [sort, setSort] = useQueryState('sort');
// URL ?page=2&sort=price is the truth — refresh-safe, shareable
return <List page={page} sort={sort} />;
}
```
### Derived state (no duplicate store)
```typescript
// ❌ duplicating filtered list in state
const [items, setItems] = useState(allItems);
const [filter, setFilter] = useState('');
useEffect(() => { setItems(allItems.filter(i => i.name.includes(filter))); }, [filter]);
// ✅ derive on render — items isn't state
const [filter, setFilter] = useState('');
const visible = useMemo(() => allItems.filter(i => i.name.includes(filter)), [filter]);
```
### Form SSOT via RHF
```typescript
const { register, watch, handleSubmit } = useForm<FormData>({
defaultValues: { email: '', plan: 'free' },
});
const plan = watch('plan'); // derived from form SSOT
return (
<form onSubmit={handleSubmit(submit)}>
<input {...register('email')} />
{plan === 'pro' && <input {...register('teamSize')} />}
</form>
);
```
### Zustand store SSOT
```typescript
import { create } from 'zustand';
type CartStore = {
items: CartItem[];
add: (item: CartItem) => void;
remove: (id: string) => void;
};
export const useCart = create<CartStore>((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
remove: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
}));
// Total derived — not stored
const useCartTotal = () => useCart((s) => s.items.reduce((a, i) => a + i.price, 0));
```
### CRDT SSOT (collaborative)
```typescript
import * as Y from 'yjs';
import { WebrtcProvider } from 'y-webrtc';
const ydoc = new Y.Doc();
new WebrtcProvider('room-id', ydoc);
const ytext = ydoc.getText('content');
ytext.observe(() => {
// single source — all peers converge
render(ytext.toString());
});
```
## 매 결정 기준
| 데이터 종류 | SSOT location |
|---|---|
| 영속 entity (user, order) | Backend DB → TanStack Query cache |
| Shareable view state | URL params (nuqs) |
| Form draft | RHF / Formik form object |
| Cross-component UI | Zustand / Jotai |
| Component-local | useState (그 component 자체 가 SSOT) |
| Collaborative | CRDT (Yjs) |
**기본값**: 매 server 의 SSOT, 매 client 의 cache projection.
## 🔗 Graph
- 부모: [[State Management]]
- 변형: [[Event Sourcing]] · [[CQRS]]
- 응용: [[React Server Components]] · [[CRDT]]
- Adjacent: [[단일 진실 공급원(Single Source of Truth) 구축]]
## 🤖 LLM 활용
**언제**: 매 state architecture design / data flow audit / bug-prone "two stores out of sync" 발견 시.
**언제 X**: 매 truly independent UI ephemeral state (hover, focus) — 매 local 이 자체 SSOT.
## ❌ 안티패턴
- **Mirror state**: 매 server 의 data 를 useState 로 복제. → 매 stale + 두 source 의 drift.
- **Multiple stores 의 same field**: Redux + Context + URL 모두 `selectedId` 보유. 매 update 의 race.
- **Premature derivation cache**: 매 derived value 를 별도 state 로 저장. 매 useMemo 충분.
- **localStorage 의 silent SSOT**: 매 sync 없는 cross-tab — 매 BroadcastChannel 또는 storage event 필요.
## 🧪 검증 / 중복
- Verified (Redux docs — "Three Principles", Dan Abramov; Martin Fowler — SSOT).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — SSOT FULL writeup |
@@ -0,0 +1,157 @@
---
id: wiki-2026-0508-단일-페이지-애플리케이션-spa
title: 단일 페이지 애플리케이션 (SPA)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [SPA, Single Page Application, 클라이언트 사이드 라우팅]
duplicate_of: none
source_trust_level: A
confidence_score: 0.93
verification_status: applied
tags: [frontend, architecture, spa, routing, react]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: TypeScript
framework: React 19 / Vite 6
---
# 단일 페이지 애플리케이션 (SPA)
## 매 한 줄
> **"매 single HTML shell 의 매 load 후 매 client-side 의 매 view transition 의 매 처리"**. 매 2010s 의 AngularJS / Backbone 의 매 시작, 매 React / Vue 의 매 mainstream 화, 매 2026 의 SPA 는 매 React 19 / 매 TanStack Router / 매 Vite 6 의 매 stack 의 매 표준. 매 SSR / 매 RSC hybrid 의 매 부상에도 매 dashboard / 매 internal tool / 매 high-interactivity app 의 매 SPA 의 매 적합.
## 매 핵심
### 매 동작 모델
- 매 initial request: 매 single `index.html` + 매 JS bundle.
- 매 client router: 매 `history.pushState` 의 매 URL 변경, 매 component 의 매 swap.
- 매 data: 매 fetch / 매 GraphQL / 매 RPC 의 매 client 직접 호출.
### 매 장점 / 매 단점
- 장점: 매 빠른 navigation (no full reload), 매 rich interaction, 매 offline 가능.
- 단점: 매 initial load 무거움, 매 SEO 취약 (매 SSR / 매 prerender 의 보완 필요), 매 JS 의존.
### 매 응용
1. Admin dashboard.
2. SaaS internal tool (Linear, Figma).
3. Realtime collaborative app.
## 💻 패턴
### Vite 6 + React 19 SPA shell
```tsx
// main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RouterProvider } from "@tanstack/react-router";
import { router } from "./router";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
```
### TanStack Router file-based routing
```tsx
// routes/__root.tsx
import { Outlet, createRootRoute } from "@tanstack/react-router";
export const Route = createRootRoute({ component: () => <Outlet /> });
// routes/users/$id.tsx
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/users/$id")({
loader: ({ params }) => fetch(`/api/users/${params.id}`).then(r => r.json()),
component: UserDetail,
});
function UserDetail() {
const user = Route.useLoaderData();
return <h1>{user.name}</h1>;
}
```
### Code splitting per route
```tsx
import { lazy } from "react";
const Settings = lazy(() => import("./pages/Settings"));
// Vite 자동 chunk 분리 + prefetch
```
### Data fetching (TanStack Query)
```tsx
import { useQuery } from "@tanstack/react-query";
function Users() {
const { data, isLoading } = useQuery({
queryKey: ["users"],
queryFn: () => fetch("/api/users").then(r => r.json()),
staleTime: 60_000,
});
if (isLoading) return <Spinner />;
return <UserList users={data} />;
}
```
### Client-side auth guard
```tsx
import { redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/_authed")({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: "/login" });
}
},
});
```
### Prerender for SEO (vite-plugin-ssr or similar)
```ts
// vite.config.ts
import { defineConfig } from "vite";
import ssr from "vite-plugin-ssr/plugin";
export default defineConfig({
plugins: [ssr({ prerender: true })],
});
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Public marketing | SSR / SSG (Next.js, Astro) — SPA 부적합 |
| Admin / dashboard | SPA 적합 |
| E-commerce | SSR + island hydration (hybrid) |
| Realtime collab | SPA + WebSocket |
**기본값**: 매 internal high-interactivity tool 은 매 SPA, 매 public 페이지는 매 SSR / 매 hybrid.
## 🔗 Graph
- 부모: [[Large_Frontend_Projects|Frontend Architecture]]
- 변형: [[SSR]] · [[SSG]] · [[Islands Architecture]] · [[React Server Components]]
- 응용: [[단일 페이지 애플리케이션(SPA) 렌더링 설계]] · [[단일 페이지 애플리케이션(SPA) 아키텍처 설계]]
- Adjacent: [[Code Splitting]] · [[Client-Side Routing]] · [[State Management]]
## 🤖 LLM 활용
**언제**: 매 highly interactive / 매 authenticated dashboard / 매 internal tool 의 매 설계 의 권장.
**언제 X**: 매 SEO 핵심 페이지 / 매 first-paint 매 critical / 매 low-end device target — 매 SSR 또는 매 hybrid 의 권장.
## ❌ 안티패턴
- **Public 마케팅 SPA**: SEO / 매 first paint 의 매 부담.
- **하나의 거대 bundle**: 매 code splitting 의 X — 매 initial load 의 매 폭증.
- **Client-only auth**: 매 sensitive route 의 매 server-side guard 의 매 부재.
## 🧪 검증 / 중복
- Verified (React 19 / Vite 6 / TanStack Router v1 docs, MDN — History API).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Vite 6 / React 19 / TanStack Router 의 modern SPA stack 정리 |
@@ -0,0 +1,34 @@
---
id: wiki-2026-0508-단일-페이지-애플리케이션-spa-렌더링-설계
title: 단일 페이지 애플리케이션(SPA) 렌더링 설계
category: 10_Wiki/Topics
status: duplicate
canonical_id: spa-rendering-architecture
duplicate_of: "[[SPA Rendering Architecture]]"
aliases: []
source_trust_level: A
confidence_score: 0.9
verification_status: redirected
tags: [duplicate, frontend, spa, rendering]
last_reinforced: 2026-05-10
github_commit: pending
---
# 단일 페이지 애플리케이션(SPA) 렌더링 설계
> **이 문서는 [[SPA Rendering Architecture]] 의 중복본입니다.** Canonical 문서로 redirect.
## 핵심 요약 (specialization aspects)
- 매 SPA = 단일 HTML shell + client-side routing + dynamic DOM updates.
- 매 CSR vs SSR vs SSG vs ISR vs Streaming SSR — 매 trade-off matrix.
- 매 2026 modern: React Server Components, Next.js 15 App Router, Remix v3, SolidStart, TanStack Router.
## 🔗 Graph
- 변형: [[React Server Components]]
- Adjacent: [[Critical Rendering Path (CRP)|Critical Rendering Path]]
## 🕓 변경 이력
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | 중복 처리 — canonical 문서로 redirect |
@@ -0,0 +1,168 @@
---
id: wiki-2026-0508-대수의-법칙-law-of-large-numbers
title: 대수의 법칙(Law of Large Numbers)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [LLN, Law of Large Numbers, 큰 수의 법칙]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [statistics, probability, frontend-analytics, ab-testing]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: analytics
---
# 대수의 법칙(Law of Large Numbers)
## 매 한 줄
> **"매 sample 수가 커질수록 sample mean 의 expected value 로의 수렴"**. 매 Bernoulli (1713) 의 weak LLN, Kolmogorov (1930) 의 strong LLN. 매 frontend analytics / A/B testing / RUM (Real User Monitoring) 의 통계적 정당성 — 매 sample 적으면 의미 X.
## 매 핵심
### 매 두 형태
- **Weak LLN**: $\bar{X}_n \xrightarrow{P} \mu$ — 매 probability convergence.
- **Strong LLN**: $\bar{X}_n \xrightarrow{a.s.} \mu$ — 매 almost sure convergence.
- 매 둘 다 finite mean μ 가정.
### 매 frontend 함의
- **A/B test sample size**: 매 N=100 의 noise 지배 — 매 N=10,000+ 필요 (effect size 의 함수).
- **Core Web Vitals p75**: 매 RUM 의 "75th percentile" — 매 N>1,000 sessions 권장 (Google).
- **Conversion rate stabilization**: 매 daily flux → weekly average 의 수렴.
- **Error rate monitoring**: 매 small traffic page 의 false alert.
### 매 응용
1. A/B test power analysis (sample size calculator).
2. Web Vitals percentile reliability.
3. Recommendation system click-through rate.
4. Survival analysis of user retention.
## 💻 패턴
### Sample size for A/B test
```typescript
// Two-proportion z-test, 80% power, α=0.05
function abTestSampleSize(
baselineRate: number,
minDetectableEffect: number,
): number {
const p1 = baselineRate;
const p2 = baselineRate + minDetectableEffect;
const pBar = (p1 + p2) / 2;
const z_alpha = 1.96; // two-sided 0.05
const z_beta = 0.84; // power 0.80
const numerator =
Math.pow(z_alpha * Math.sqrt(2 * pBar * (1 - pBar)) +
z_beta * Math.sqrt(p1 * (1 - p1) + p2 * (1 - p2)), 2);
return Math.ceil(numerator / Math.pow(p2 - p1, 2));
}
// Baseline 5% conversion, want to detect +1 percentage point lift
console.log(abTestSampleSize(0.05, 0.01)); // ~3,000 per arm
```
### Running mean (LLN visualizer)
```typescript
function* runningMean(samples: Iterable<number>) {
let n = 0;
let mean = 0;
for (const x of samples) {
n += 1;
mean += (x - mean) / n; // Welford
yield { n, mean };
}
}
// Coin flip (true mean = 0.5)
const flips = Array.from({ length: 10000 }, () => (Math.random() < 0.5 ? 1 : 0));
for (const { n, mean } of runningMean(flips)) {
if (n % 1000 === 0) console.log(`n=${n}, mean=${mean.toFixed(4)}`);
}
// n=1000 mean ≈ 0.49
// n=10000 mean ≈ 0.50 (LLN convergence)
```
### Web Vitals percentile reliability check
```typescript
import { onLCP } from 'web-vitals';
const lcpSamples: number[] = [];
onLCP((metric) => {
lcpSamples.push(metric.value);
if (lcpSamples.length >= 1000) {
const sorted = [...lcpSamples].sort((a, b) => a - b);
const p75 = sorted[Math.floor(sorted.length * 0.75)];
sendBeacon({ p75, n: lcpSamples.length });
}
});
// p75 trustworthy only after N>1,000 (Google CrUX guidance)
```
### Bayesian early-stopping (avoid LLN trap)
```typescript
// Don't peek at A/B test before sample size reached!
function shouldStop(arm: { successes: number; trials: number }, target: number) {
if (arm.trials < target) return false;
// proceed to analysis
return true;
}
```
### Bootstrap confidence interval
```typescript
function bootstrapCI(samples: number[], B = 10000, alpha = 0.05) {
const means: number[] = [];
for (let b = 0; b < B; b++) {
let sum = 0;
for (let i = 0; i < samples.length; i++) {
sum += samples[Math.floor(Math.random() * samples.length)];
}
means.push(sum / samples.length);
}
means.sort((a, b) => a - b);
return [
means[Math.floor(B * (alpha / 2))],
means[Math.floor(B * (1 - alpha / 2))],
];
}
```
## 매 결정 기준
| 상황 | Sample size guideline |
|---|---|
| Web Vitals p75 (Google CrUX) | N > 1,000 sessions per page |
| A/B test (5% baseline, 1pp lift) | ~3,000 per arm |
| Click-through rate stabilization | N > 10,000 impressions |
| Error rate monitoring (rare events) | Apply Poisson, not LLN naively |
**기본값**: 매 결과 보고 전 N≥1,000 — 매 LLN safety zone.
## 🔗 Graph
- 부모: [[Probability Theory]] · [[Statistical Inference]]
- 응용: [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]]
- Adjacent: [[Monte Carlo Methods]]
## 🤖 LLM 활용
**언제**: 매 sample size 결정 / 매 metric 의 reliability 의 statistical 정당화 / 매 small-N false-positive 의 진단.
**언제 X**: 매 비-i.i.d. data (autocorrelated time series) — 매 LLN naive 적용 X. 매 stationarity 확인.
## ❌ 안티패턴
- **Peeking at A/B test**: 매 N=50 에서 "winner" 선언 — 매 LLN 미달 + multiple testing.
- **Rare event LLN**: 매 0.01% conversion → 매 N=1000 의 평균 0 가능. 매 Poisson 필요.
- **Heavy-tail distribution**: 매 Cauchy (no finite mean) — 매 LLN 미적용.
- **Selection bias**: 매 sample 이 random 이 X — 매 N 무관 의 biased estimate.
## 🧪 검증 / 중복
- Verified (Kolmogorov, "Foundations of Probability"; Google web.dev — Web Vitals reporting).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — LLN with frontend analytics applications |
@@ -0,0 +1,204 @@
---
id: wiki-2026-0508-디지털-트윈-및-데이터-시뮬레이션
title: 디지털 트윈 및 데이터 시뮬레이션
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Digital Twin, IoT Visualization, Real-time Simulation]
duplicate_of: none
source_trust_level: A
confidence_score: 0.85
verification_status: applied
tags: [frontend, digital-twin, threejs, websocket, visualization]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react-three-fiber
---
# 디지털 트윈 및 데이터 시뮬레이션
## 매 한 줄
> **"매 물리 자산의 live mirror"**. 공장 / 빌딩 / 차량을 3D로 render + 실시간 sensor stream 으로 동기화. 2026 stack: React Three Fiber + WebSocket / WebTransport + WebGPU compute + Cesium (geo).
## 매 핵심
### 매 디지털 트윈이란
- 물리 system 의 software replica — sensor 로 state sync, simulation 으로 future predict.
- 4 단계: Descriptive (현재 시각화) → Diagnostic (anomaly detect) → Predictive (forecast) → Prescriptive (optimize).
- Use case: smart factory, BIM, fleet management, energy grid.
### 매 frontend 책임
- **3D rendering**: 자산 model (glTF/USD) load + scene graph.
- **Real-time stream**: WebSocket / SSE / WebTransport 으로 sensor data.
- **Time-series viz**: 매 chart + 3D overlay (heatmap, particles).
- **Interaction**: select asset → 매 detail panel + control commands.
### 매 응용
1. 공장 라인 모니터링 (machine health).
2. 빌딩 BIM + HVAC sensor.
3. Fleet tracking (차량 GPS + telematics).
4. Energy grid load visualization.
## 💻 패턴
### React Three Fiber + glTF asset
```tsx
import { Canvas, useFrame } from '@react-three/fiber';
import { useGLTF, OrbitControls } from '@react-three/drei';
function Factory() {
const { scene } = useGLTF('/models/factory.glb');
return <primitive object={scene} />;
}
export function Twin() {
return (
<Canvas camera={{ position: [10, 10, 10] }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 10, 5]} />
<Factory />
<SensorOverlay />
<OrbitControls />
</Canvas>
);
}
```
### WebSocket sensor stream → state
```tsx
import { create } from 'zustand';
const useSensors = create<{ sensors: Record<string, Sensor> }>(() => ({
sensors: {},
}));
function useSensorStream(url: string) {
useEffect(() => {
const ws = new WebSocket(url);
ws.onmessage = e => {
const { id, value, timestamp } = JSON.parse(e.data);
useSensors.setState(s => ({
sensors: { ...s.sensors, [id]: { value, timestamp } },
}));
};
return () => ws.close();
}, [url]);
}
```
### Sensor heatmap on 3D mesh
```tsx
function SensorOverlay() {
const sensors = useSensors(s => s.sensors);
return (
<>
{Object.entries(sensors).map(([id, sensor]) => (
<mesh key={id} position={sensor.position}>
<sphereGeometry args={[0.2, 16, 16]} />
<meshStandardMaterial
color={tempToColor(sensor.value)}
emissive={tempToColor(sensor.value)}
emissiveIntensity={0.5}
/>
</mesh>
))}
</>
);
}
function tempToColor(t: number) {
// 매 cold blue → hot red
const h = (1 - Math.min(t / 100, 1)) * 240;
return `hsl(${h}, 100%, 50%)`;
}
```
### Time-series chart + 3D selection sync
```tsx
const [selectedAsset, setSelectedAsset] = useState<string | null>(null);
<Asset onClick={() => setSelectedAsset('pump-3')} />
{selectedAsset && (
<DetailPanel>
<TimeSeriesChart sensorId={selectedAsset} />
</DetailPanel>
)}
```
### WebGPU compute for particle simulation
```ts
// 매 air flow / thermal simulation 100k particles.
const computeShader = `
@group(0) @binding(0) var<storage, read_write> particles: array<vec4f>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
let i = id.x;
particles[i].xyz += particles[i].xyz * 0.01; // 매 velocity update
}`;
```
### Cesium for geo-scale twin
```tsx
import { Viewer, Entity } from 'resium';
<Viewer full>
{fleet.map(vehicle => (
<Entity
key={vehicle.id}
position={Cartesian3.fromDegrees(vehicle.lon, vehicle.lat, vehicle.alt)}
model={{ uri: '/models/truck.glb', scale: 1.0 }}
/>
))}
</Viewer>
```
### Anomaly detection (client-side)
```ts
function detectAnomaly(values: number[]): boolean {
const mean = values.reduce((a, b) => a + b) / values.length;
const std = Math.sqrt(values.reduce((s, v) => s + (v - mean) ** 2, 0) / values.length);
const last = values[values.length - 1];
return Math.abs(last - mean) > 3 * std; // 매 3-sigma
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Indoor (factory/building) | React Three Fiber + glTF. |
| Outdoor / geo-scale | Cesium / MapLibre 3D. |
| < 1k sensors | WebSocket + zustand. |
| 100k+ data points | WebGPU compute + instanced mesh. |
| Forecasting | server-side (TimeGPT / Prophet) → push results. |
| Safety-critical | unidirectional viz only — control via separate verified channel. |
**기본값**: R3F + WebSocket + zustand + recharts. WebGPU 는 particle/heatmap 만.
## 🔗 Graph
- 부모: [[클라우드_인프라_및_IaC_운영_표준|IoT]]
- 변형: [[GIS]]
- Adjacent: [[WebGPU]] · [[Three.js]] · [[Cesium]]
## 🤖 LLM 활용
**언제**: 물리 system live mirror, 감독자 dashboard, what-if simulation.
**언제 X**: static infographic, 매 control loop (latency-critical 은 PLC/edge).
## ❌ 안티패턴
- **High-poly raw model**: 100M tri 의 CAD model 그대로 load → 매 GPU 죽음. Decimate → 100k tri.
- **Per-sensor mesh**: 10k sensor 의 sphere 개별 mesh → 매 instanced mesh 사용.
- **Polling**: 매 1초마다 REST GET → WebSocket / SSE.
- **Control via twin UI**: viz 와 control 분리. 매 safety-critical 명령은 verified channel.
- **No level of detail**: 멀리 있는 asset 도 풀 detail — LOD 필수.
## 🧪 검증 / 중복
- Verified (NIST digital twin definition, R3F docs, Cesium docs, WebGPU spec).
- 신뢰도 A-.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — R3F + WebSocket + WebGPU stack |
@@ -0,0 +1,177 @@
---
id: wiki-2026-0508-렌더링-블로킹-방지를-위한-css-분할-및-로딩-최적화
title: 렌더링 블로킹 방지를 위한 CSS 분할 및 로딩 최적화
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Critical CSS, CSS Code Splitting, Render-blocking CSS]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
verification_status: applied
tags: [frontend, css, performance, critical-css, lcp]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: css
framework: 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)
```html
<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)
```ts
// 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)
```html
<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)
```tsx
// app/products/page.tsx — 매 자동으로 별도 CSS chunk.
import './products.css'; // 매 only loaded on /products.
```
### CSS Modules (component-scoped)
```tsx
import styles from './Button.module.css';
<button className={styles.primary}>Click</button>;
// 매 build 시 hash → only used styles bundled.
```
### Tailwind v4 (oxide engine, 2026)
```css
/* main.css */
@import "tailwindcss";
@theme {
--color-brand: oklch(0.7 0.2 250);
}
```
PurgeCSS 자동 — 매 unused class 제거. 최종 bundle 5-10KB.
### Preload key fonts + CSS together
```html
<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
```css
.card {
contain: content; /* 매 layout + paint + style 격리 */
content-visibility: auto;
contain-intrinsic-size: 0 200px;
}
```
### HTTP/2 push 대체 — 103 Early Hints
```http
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)
```bash
# 매 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
- 부모: [[Web Performance]] · [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]]
- 변형: [[Critical CSS]] · [[CSS_Architecture_and_Styling|CSS-in-JS]] · [[CSS Modules]]
- 응용: [[Tailwind v4]] · [[Vite]] · [[Next.js]]
## 🤖 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 |
@@ -0,0 +1,161 @@
---
id: wiki-2026-0508-렌더링-차단-리소스-render-blocking-resou
title: 렌더링 차단 리소스 (Render-blocking Resources)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Render Blocking, Critical Resources, Critical Rendering Path]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [performance, web-vitals, rendering, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: html
framework: browser
---
# 렌더링 차단 리소스 (Render-blocking Resources)
## 매 한 줄
> **"매 render-blocking = First Paint 까지 매 다운로드/parse 필수 리소스"**. 매 head 안 sync `<script>` + 매 default `<link rel="stylesheet">` — 매 둘 다 차단. 매 2026 modern = 매 critical CSS inline + `defer/async` script + `media` query split + 매 LCP 최적화.
## 매 핵심
### 매 차단 리소스
- `<script src>` (no `defer`/`async`) — 매 HTML parse 차단.
- `<link rel="stylesheet">` — 매 render 차단 (parse 차단 의 X).
- `@import` in CSS — 매 추가 차단.
- Web fonts — 매 FOIT/FOUT 매 paint 지연.
### 매 비차단 만들기
- `<script defer>` — DOMContentLoaded 직전 실행, parse 차단 X.
- `<script async>` — 다운로드 즉시 실행, parse 차단 가능.
- `<link rel="preload">` — early discovery.
- `media` attribute — 매 print/non-matching media — 매 비차단.
- Critical CSS inline + 매 rest async load.
### 매 영향 metric
- **FCP** (First Contentful Paint).
- **LCP** (Largest Contentful Paint).
- **TBT** (Total Blocking Time).
## 💻 패턴
### Defer vs async
```html
<!-- 매 GOOD — 매 page-essential, parse 비차단 -->
<script defer src="/app.js"></script>
<!-- 매 GOOD — 매 independent (analytics) -->
<script async src="https://analytics.example.com/track.js"></script>
<!-- 매 BAD — 매 parse 차단 -->
<script src="/app.js"></script>
```
### Critical CSS inline
```html
<head>
<style>
/* 매 above-the-fold 만 — 매 14KB 이하 — 매 1RTT */
body { margin: 0; font-family: system-ui; }
.hero { min-height: 60vh; background: #111; color: #fff; }
</style>
<link rel="preload" href="/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/main.css"></noscript>
</head>
```
### media query split
```html
<!-- 매 print — 매 즉시 비차단 -->
<link rel="stylesheet" href="print.css" media="print">
<!-- 매 wide screen 만 — small 매 비차단 -->
<link rel="stylesheet" href="desktop.css" media="(min-width: 1024px)">
```
### Preload critical font
```html
<link rel="preload" href="/fonts/Inter.var.woff2" as="font"
type="font/woff2" crossorigin>
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter.var.woff2') format('woff2-variations');
font-display: swap; /* 매 FOUT — 매 paint 차단 X */
}
</style>
```
### Modulepreload (ES modules)
```html
<link rel="modulepreload" href="/app.js">
<script type="module" src="/app.js"></script>
```
### Resource hints
```html
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
```
### Speculation Rules (Chrome 121+)
```html
<script type="speculationrules">
{
"prerender": [{ "where": { "href_matches": "/article/*" }, "eagerness": "moderate" }]
}
</script>
```
### Fetch priority
```html
<img src="hero.webp" fetchpriority="high" alt="...">
<script src="non-critical.js" defer fetchpriority="low"></script>
```
## 매 결정 기준
| 리소스 | 처리 |
|---|---|
| Critical CSS (above fold) | inline `<style>` |
| Non-critical CSS | preload + onload swap, OR media query split |
| App JS (DOM-dependent) | `defer` |
| 3rd-party analytics | `async` |
| Web font (critical) | preload + `font-display: swap` |
| LCP image | `fetchpriority="high"` |
| Below-fold image | `loading="lazy"` |
**기본값**: Critical CSS inline (≤14KB), 매 script `defer`, font preload + swap, LCP image high priority.
## 🔗 Graph
- 부모: [[Web Performance]] · [[Critical Rendering Path (CRP)|Critical Rendering Path]]
- 변형: [[Critical CSS]]
- 응용: [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]]
- Adjacent: [[Speculation Rules]]
## 🤖 LLM 활용
**언제**: Lighthouse audit 분석, "render blocking" 경고 해결, critical CSS 추출.
**언제 X**: 매 specific framework SSR config — 매 framework docs 매 더 정확.
## ❌ 안티패턴
- **모든 CSS head sync**: 매 critical 만 inline.
- **`<script>` head sync**: 매 `defer` 의 사용.
- **`@import` chain**: 매 sequential 차단 — 매 `<link>` 의 사용.
- **font-display: block** (default): 매 FOIT 매 3s — 매 `swap`.
- **preload 남발**: 매 5개+ — 매 priority 무의미 — 매 critical 만.
## 🧪 검증 / 중복
- Verified (web.dev, MDN Performance, Lighthouse audits, Chrome DevRel).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — defer/async + critical CSS + preload |
@@ -0,0 +1,180 @@
---
id: wiki-2026-0508-모바일-퍼스트-mobile-first
title: 모바일 퍼스트(Mobile-First)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Mobile-First, 모바일 우선, Mobile First Design, 모바일 우선주의]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, responsive, css, mobile, design]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: css
framework: tailwind
---
# 모바일 퍼스트(Mobile-First)
## 매 한 줄
> **"매 작은 화면부터 design — 큰 화면 의 progressive enhancement"**. Luke Wroblewski (2009)가 제창, smallest viewport 기준 baseline CSS, larger breakpoint 의 `min-width` media query 의 progressive enhancement. 매 2026 의 mobile traffic 60%+ 환경의 default approach.
## 매 핵심
### 매 vs Desktop-first
| | Mobile-first | Desktop-first |
|---|---|---|
| Baseline | smallest viewport | largest viewport |
| Media query | `min-width` (up) | `max-width` (down) |
| Mindset | progressive enhancement | graceful degradation |
| Default 2026 | ✓ | (legacy) |
### 매 3원칙 (Wroblewski)
1. **Constraints force focus** — small screen 매 essential content prioritize.
2. **Capabilities expand** — touch, GPS, camera 의 leverage.
3. **Progressive enhancement** — base 의 small + add 의 large.
### 매 응용
1. **Performance budget** — mobile network slow → minimal payload first.
2. **Touch-first UX** — 44×44px tap target (Apple HIG), 48×48dp (Material).
3. **Content hierarchy** — most important top, fold concept abandoned.
## 💻 패턴
### 1. CSS mobile-first media query
```css
/* base — mobile (< 640px implicit) */
.container {
padding: 1rem;
font-size: 14px;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
/* tablet ≥ 640px */
@media (min-width: 640px) {
.container { padding: 2rem; font-size: 16px; }
.grid { grid-template-columns: repeat(2, 1fr); }
}
/* desktop ≥ 1024px */
@media (min-width: 1024px) {
.grid { grid-template-columns: repeat(3, 1fr); gap: 2rem; }
}
```
### 2. Tailwind mobile-first utilities
```tsx
// base 의 mobile, sm:/md:/lg: 의 enhancement
export function Card() {
return (
<div className="p-4 sm:p-6 lg:p-8">
<h2 className="text-xl sm:text-2xl lg:text-3xl">Title</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Item />
<Item />
<Item />
</div>
</div>
);
}
```
### 3. Container queries (modern 2026)
```css
.card-container {
container-type: inline-size;
container-name: card;
}
.card { display: flex; flex-direction: column; }
@container card (min-width: 400px) {
.card { flex-direction: row; }
}
```
### 4. Responsive image (srcset)
```html
<img
src="hero-mobile.webp"
srcset="hero-mobile.webp 480w, hero-tablet.webp 1024w, hero-desktop.webp 1920w"
sizes="(min-width: 1024px) 1920px, (min-width: 640px) 1024px, 100vw"
alt="Hero"
loading="lazy"
/>
```
### 5. Viewport meta + safe area
```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
```
```css
.app {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
```
### 6. Touch-friendly tap targets
```css
button, a {
min-width: 44px;
min-height: 44px;
padding: 0.75rem 1rem;
/* Apple HIG: 44×44pt minimum */
}
```
### 7. Performance: critical CSS inline
```html
<head>
<style>/* critical above-fold CSS, < 14KB */</style>
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
</head>
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| New project 2026 | mobile-first default. |
| Legacy desktop site | gradual migration via container queries. |
| Internal tool (desktop only) | desktop-first 도 OK 매 — but mobile-first 매 future-proof. |
| Component library | container queries > viewport media queries (component reusability). |
**기본값**: Tailwind mobile-first utilities + container queries 매 component-level.
## 🔗 Graph
- 변형: [[컨테이너 쿼리 (Container Queries)]]
- 응용: [[CSS_Architecture_and_Styling|Tailwind CSS]]
- Adjacent: [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]]
## 🤖 LLM 활용
**언제**: 새 web project, e-commerce, content site, public-facing app, PWA.
**언제 X**: desktop-only enterprise tool 의 absolute desktop-first 가 효율적인 case (rare).
## ❌ 안티패턴
- **`max-width` 만 의 의존**: 매 reverse mobile-first defeats purpose.
- **고정 px 의 layout**: rem/em/% 의 use.
- **Hover-only interactions**: touch 의 hover 없음 — tap fallback 필수.
- **Fold obsession**: 매 mobile 의 scrolling natural — fold 없음.
- **Tap target < 44px**: thumb miss-tap.
## 🧪 검증 / 중복
- Verified (Wroblewski "Mobile First" 2011, Google Web.dev 2026, MDN responsive design).
- 신뢰도 A.
- Canonical 매 [[모바일 퍼스트(Mobile-First)]] — 매 [[모바일 우선주의 (Mobile-First) 디자인]], [[모바일 퍼스트 및 다양한 디바이스 환경을 위한 반응형 레이아웃 구축]] redirect 매 here.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — canonical full content + 7 patterns (CSS, Tailwind, container queries) |
@@ -0,0 +1,178 @@
---
id: wiki-2026-0508-비동기-데이터-관리
title: 비동기 데이터 관리
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Async Data Management, Server State, Data Fetching]
duplicate_of: none
source_trust_level: A
confidence_score: 0.92
verification_status: applied
tags: [frontend, async, react-query, swr, server-state]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react-query
---
# 비동기 데이터 관리
## 매 한 줄
> **"매 server state 의 client cache 의 separation"**. 매 fetch / cache / sync / invalidate / retry / dedupe 의 7 concerns 의 library 의 delegation — 매 2026 의 TanStack Query (v5) / SWR / RTK Query 의 standard. Custom `useEffect + fetch` 의 anti-pattern.
## 매 핵심
### 매 server vs client state
- **client state**: form input, modal open/close, theme — local, ephemeral. Zustand/useState.
- **server state**: 매 remote source of truth — async, stale, shared, cached. TanStack Query.
### 매 7 concerns
1. **Fetch**: HTTP request + abort.
2. **Cache**: key-based store.
3. **Dedupe**: 매 simultaneous request 의 share.
4. **Stale**: time-based freshness.
5. **Background refetch**: window focus, reconnect.
6. **Retry**: exponential backoff.
7. **Invalidate**: mutation 후 refetch.
### 매 query lifecycle
```
fetching → fresh (staleTime) → stale → refetch → fresh
gcTime → garbage collect
```
## 💻 패턴
### TanStack Query v5 (React)
```tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserProfile({ id }: { id: string }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', id],
queryFn: ({ signal }) => fetch(`/api/users/${id}`, { signal }).then(r => r.json()),
staleTime: 60_000, // 1min fresh
gcTime: 5 * 60_000, // 5min cache
});
if (isLoading) return <Skeleton />;
if (error) return <Error error={error} />;
return <Profile user={data} />;
}
```
### Mutation + invalidation
```tsx
function useUpdateUser() {
const qc = useQueryClient();
return useMutation({
mutationFn: (user: User) =>
fetch(`/api/users/${user.id}`, {
method: 'PUT', body: JSON.stringify(user),
}).then(r => r.json()),
onSuccess: (_, user) => {
qc.invalidateQueries({ queryKey: ['user', user.id] });
qc.invalidateQueries({ queryKey: ['users'] });
},
});
}
```
### Optimistic update
```tsx
useMutation({
mutationFn: toggleTodo,
onMutate: async (id) => {
await qc.cancelQueries({ queryKey: ['todos'] });
const prev = qc.getQueryData(['todos']);
qc.setQueryData(['todos'], (old: Todo[]) =>
old.map(t => t.id === id ? { ...t, done: !t.done } : t));
return { prev };
},
onError: (_, __, ctx) => qc.setQueryData(['todos'], ctx?.prev),
onSettled: () => qc.invalidateQueries({ queryKey: ['todos'] }),
});
```
### Infinite scroll
```tsx
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) =>
fetch(`/api/posts?cursor=${pageParam}`).then(r => r.json()),
getNextPageParam: (last) => last.nextCursor,
initialPageParam: 0,
});
```
### Suspense mode (React 19)
```tsx
const { data } = useSuspenseQuery({
queryKey: ['user', id],
queryFn: fetchUser,
});
// data 의 always defined — Suspense boundary 의 loading 의 handle
```
### SWR (lightweight alternative)
```tsx
import useSWR from 'swr';
const { data, error, mutate } = useSWR(
`/api/users/${id}`,
(url) => fetch(url).then(r => r.json()),
{ revalidateOnFocus: true, dedupingInterval: 2000 }
);
```
### Server Components data (Next.js 15 / RSC)
```tsx
// app/users/[id]/page.tsx — runs on server
async function UserPage({ params }: { params: { id: string } }) {
const user = await fetch(`https://api/users/${params.id}`, {
next: { revalidate: 60, tags: [`user-${params.id}`] }
}).then(r => r.json());
return <Profile user={user} />;
}
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| React app | TanStack Query v5 |
| Next.js App Router | RSC fetch + Server Actions + tag invalidation |
| Redux app | RTK Query (Redux 의 통합) |
| 매 minimal bundle | SWR (~5KB) |
| 매 GraphQL | Apollo Client / urql |
| 매 simple form fetch | native `fetch` + `useState` 의 OK |
**기본값**: 매 React + REST → TanStack Query, 매 Next.js → RSC fetch.
## 🔗 Graph
- 부모: [[State Management]]
- 응용: [[Optimistic UI]] · [[Infinite Scroll]] · [[React Server Components]]
- Adjacent: [[AbortController]]
## 🤖 LLM 활용
**언제**: cache key design 의 review, invalidation strategy 의 plan, race condition 의 debug.
**언제 X**: real-time data (WebSocket/SSE 의 substitute).
## ❌ 안티패턴
- **`useEffect + fetch`**: 매 race condition, 매 no dedup, 매 no cache — 매 library 의 use.
- **Global Redux 에 server state**: 매 manual cache management — 매 RTK Query 의 use.
- **Polling 의 abuse**: 매 SSE/WebSocket 의 substitute.
- **`enabled: !!id` 누락**: 매 undefined 의 fetch — false positive.
## 🧪 검증 / 중복
- Verified (TanStack Query v5 docs, Vercel SWR docs, Next.js 15 data fetching).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — TanStack Query v5 + RSC + optimistic update 의 정리 |
@@ -0,0 +1,162 @@
---
id: wiki-2026-0508-유지보수-가능한-대규모-프론트엔드-css-설계
title: 유지보수 가능한 대규모 프론트엔드 CSS 설계
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Scalable CSS Architecture, CSS at Scale, Design System CSS]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, css, architecture, design-system]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: css
framework: tailwind-css
---
# 유지보수 가능한 대규모 프론트엔드 CSS 설계
## 매 한 줄
> **"매 CSS 는 매 N 명 의 개발자 가 매 6개월 후 의 코드 base 에서 매 두려움 없이 수정할 수 있어야 한다"**. 매 대규모 CSS 의 적은 매 specificity 폭주, 매 dead code, 매 inconsistent spacing. 매 2026 의 정답 — utility-first (Tailwind 4) + design tokens + CSS Layers + container queries.
## 매 핵심
### 매 4 개 의 추상 레벨
- **Tokens**: color, spacing, typography 의 매 raw value (CSS custom property).
- **Primitives**: Box, Stack, Grid 의 매 layout primitive.
- **Components**: Button, Card, Modal — 매 design system 의 unit.
- **Patterns**: Page-level 조합.
### 매 방법론 비교
- **BEM**: `.block__element--modifier`. 매 명시적이지만 매 verbose.
- **CSS Modules**: 매 file-scoped, 매 collision 없음.
- **CSS-in-JS**: styled-components, Emotion — 매 runtime cost.
- **Utility-first (Tailwind)**: 매 2026 의 default — 매 zero runtime, JIT.
- **Vanilla Extract / Panda**: 매 typed CSS, build-time.
### 매 응용
1. Design System 구축 (Material, Ant, Chakra).
2. Multi-brand whitelabel (token swap).
3. Dark mode / theming.
## 💻 패턴
### Design tokens (CSS Custom Properties)
```css
:root {
--color-primary-500: oklch(60% 0.2 270);
--space-1: 0.25rem;
--space-2: 0.5rem;
--radius-md: 0.5rem;
--font-body: "Inter", system-ui, sans-serif;
}
[data-theme="dark"] {
--color-primary-500: oklch(75% 0.2 270);
}
```
### CSS Layers (cascade 제어)
```css
@layer reset, base, components, utilities;
@layer reset { *, *::before, *::after { box-sizing: border-box; } }
@layer base { body { font-family: var(--font-body); } }
@layer components { .btn { padding: var(--space-2); } }
@layer utilities { .mt-4 { margin-top: var(--space-4); } }
```
### Tailwind 4 (CSS-first config)
```css
@import "tailwindcss";
@theme {
--color-brand-500: oklch(60% 0.2 270);
--spacing: 0.25rem;
}
```
```html
<button class="bg-brand-500 px-4 py-2 rounded-md text-white">매 Click</button>
```
### Container queries (매 layout-aware component)
```css
.card-container { container-type: inline-size; }
.card { display: grid; grid-template-columns: 1fr; }
@container (min-width: 30rem) {
.card { grid-template-columns: 200px 1fr; }
}
```
### 컴포넌트 + variants (CVA)
```typescript
import { cva } from "class-variance-authority";
export const button = cva("rounded-md font-medium", {
variants: {
intent: {
primary: "bg-brand-500 text-white",
ghost: "bg-transparent text-brand-500",
},
size: { sm: "px-2 py-1", md: "px-4 py-2", lg: "px-6 py-3" },
},
defaultVariants: { intent: "primary", size: "md" },
});
```
### Logical properties (i18n-ready)
```css
.card {
padding-inline: var(--space-4); /* LTR / RTL 모두 동작 */
margin-block-end: var(--space-2);
border-inline-start: 2px solid var(--color-accent);
}
```
### Style scoping (CSS Modules)
```typescript
import styles from "./Card.module.css";
export const Card = ({ children }) => <div className={styles.card}>{children}</div>;
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| New project (2026) | Tailwind 4 + CVA + design tokens |
| Existing BEM codebase | 매 점진적 migration + Layers |
| Component library 출시 | CSS Modules or Vanilla Extract |
| Multi-brand SaaS | CSS custom properties + theme swap |
**기본값**: **Tailwind 4 + CVA + CSS custom property tokens + Container queries**.
## 🔗 Graph
- 부모: [[CSS_Architecture_and_Styling|CSS Architecture]] · [[Design System]]
- 변형: [[BEM]] · [[CSS Modules]] · [[CSS_Architecture_and_Styling|CSS-in-JS]]
- 응용: [[CSS_Architecture_and_Styling|Tailwind CSS]] · [[Vanilla Extract]] · [[Theming]]
- Adjacent: [[컨테이너 쿼리 (Container Queries)|Container Queries]]
## 🤖 LLM 활용
**언제**: 매 design system 구축, 매 large team, 매 multi-brand product.
**언제 X**: 매 single-page landing, 매 prototype.
## ❌ 안티패턴
- **Specificity 전쟁**: `!important` 남발 → 매 cascade 붕괴.
- **Magic numbers**: `padding: 13px` — 매 token 미사용.
- **Global selector overuse**: `div > p > span` — 매 brittle.
- **Dead CSS**: 매 unused selector 누적 → 매 bundle bloat.
## 🧪 검증 / 중복
- Verified (Tailwind 4 docs, MDN CSS Layers, CSS Tricks).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — scalable CSS 7 patterns |
@@ -0,0 +1,169 @@
---
id: wiki-2026-0508-컴포넌트-기반-웹-프레임워크-아키텍처-설계
title: 컴포넌트 기반 웹 프레임워크 아키텍처 설계
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Component Framework Architecture, Web Framework Design]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, architecture, framework-design, react, vue, svelte]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react-19/vue-3.5/svelte-5
---
# 컴포넌트 기반 웹 프레임워크 아키텍처 설계
## 매 한 줄
> **"매 reactive component tree + diffing/scheduling 의 framework design"**. 매 React/Vue/Svelte 모두 (1) component model, (2) reactivity primitive, (3) rendering scheduler, (4) state management, (5) routing/data layer 의 5-layer stack. 매 2026 의 trend — fine-grained reactivity (Svelte 5 runes, Vue 3.5 Vapor, Solid signals) 의 dominant.
## 매 핵심
### 매 5-layer stack
1. **Component model**: function (React/Solid) vs SFC (Vue/Svelte) vs class.
2. **Reactivity primitive**: VDOM diff vs signals vs compile-time reactivity.
3. **Scheduler**: sync vs concurrent (React 19) vs microtask-batched.
4. **State**: local state, context, external store (Zustand, Pinia, Redux Toolkit).
5. **Data/routing**: Next.js App Router, Nuxt, SvelteKit, Remix.
### 매 reactivity spectrum (2026)
- **VDOM diff** (React, Preact): re-run component → diff → patch.
- **Fine-grained signals** (Solid, Svelte 5 runes, Vue 3.5 Vapor): track reads/writes, surgical DOM update.
- **Compile-time** (Svelte, Marko): compile component to imperative DOM ops.
### 매 응용
1. Internal framework / DSL design.
2. Framework-agnostic component library (Lit, Web Components).
3. Custom renderer (React Native, react-three-fiber, Ink).
## 💻 패턴
### 1. Minimal signal-based reactivity (~Solid)
```typescript
type Signal<T> = [() => T, (v: T) => void];
let currentSub: (() => void) | null = null;
function signal<T>(initial: T): Signal<T> {
let value = initial;
const subs = new Set<() => void>();
const get = () => {
if (currentSub) subs.add(currentSub);
return value;
};
const set = (v: T) => { value = v; subs.forEach(s => s()); };
return [get, set];
}
function effect(fn: () => void) {
const run = () => { currentSub = run; fn(); currentSub = null; };
run();
}
```
### 2. VDOM diff core (~Preact)
```typescript
interface VNode { type: string | Function; props: any; children: VNode[]; }
function diff(oldV: VNode | null, newV: VNode, parent: HTMLElement) {
if (!oldV) parent.appendChild(create(newV));
else if (oldV.type !== newV.type) parent.replaceChild(create(newV), parent.firstChild!);
else updateProps(parent.firstChild as HTMLElement, oldV.props, newV.props);
}
```
### 3. Component as function (React-style)
```typescript
function Counter() {
const [count, setCount] = useState(0);
return h('button', { onClick: () => setCount(count + 1) }, `Count: ${count}`);
}
```
### 4. Compile-time reactivity (~Svelte 5 runes)
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => { console.log(count); });
</script>
<button onclick={() => count++}>{count} / {doubled}</button>
```
### 5. Scheduler (concurrent React-like)
```typescript
const queue: Array<() => void> = [];
let scheduled = false;
function schedule(work: () => void) {
queue.push(work);
if (!scheduled) {
scheduled = true;
queueMicrotask(() => {
while (queue.length) queue.shift()!();
scheduled = false;
});
}
}
```
### 6. Custom renderer registry
```typescript
interface Renderer<HostNode> {
createElement(type: string): HostNode;
appendChild(parent: HostNode, child: HostNode): void;
setProp(node: HostNode, key: string, value: unknown): void;
}
// React reconciler / Vue createRenderer 의 패턴.
```
### 7. Compound component pattern
```tsx
<Tabs>
<Tabs.List>
<Tabs.Trigger value="a">A</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="a">...</Tabs.Content>
</Tabs>
```
## 매 결정 기준
| 상황 | Choice |
|---|---|
| Mass-market app | React 19 + Next.js 15 (ecosystem) |
| Performance-critical | Solid / Svelte 5 (signals) |
| Progressive enhancement | Astro + island arch |
| Web Components / portable | Lit |
| Embedded UI / DSL | Custom renderer atop React reconciler |
**기본값**: 매 React 19 + Server Components + Suspense (mass-market). 매 perf-bound 의 Solid/Svelte 5.
## 🔗 Graph
- 부모: [[Component-Composition|Component-Based Architecture]]
- 변형: [[Virtual_DOM과_Reconciliation|Virtual DOM]] · [[Fine-Grained Reactivity]]
- 응용: [[React]] · [[Solid]]
- Adjacent: [[State Management]] · [[Modern_Web_Rendering_and_Optimization|Server Components]] · [[Hydration]]
## 🤖 LLM 활용
**언제**: 새 framework / DSL 설계 시. 매 framework choice trade-off discussion 시.
**언제 X**: 매 단순 app 개발 — 매 ecosystem (Next.js, Nuxt) defaults 에 의존.
## ❌ 안티패턴
- **재발명 반복**: 매 production framework 와 경쟁 의도 — 매 절대 X.
- **VDOM 의 abuse**: 매 fine-grained 가 더 적합한 경우에도 VDOM 강행.
- **Scheduler omission**: 매 sync only — 매 large tree 의 long task 발생.
- **Tight coupling renderer ↔ reactivity**: 매 portability 상실.
## 🧪 검증 / 중복
- Verified (React 19, Vue 3.5 Vapor, Svelte 5 runes, Solid 1.x docs).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — 5-layer stack + 7 patterns + 2026 reactivity landscape |
@@ -0,0 +1,163 @@
---
id: wiki-2026-0508-콘텐츠-기반의-이커머스-및-뉴스-웹사이트-성능-튜닝
title: 콘텐츠 기반의 이커머스 및 뉴스 웹사이트 성능 튜닝
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Content Site Performance, E-commerce Tuning, News Site Tuning]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, performance, ecommerce, news, isr, edge-caching]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: nextjs
---
# 콘텐츠 기반의 이커머스 및 뉴스 웹사이트 성능 튜닝
## 매 한 줄
> **"매 이커머스 와 뉴스 는 매 LCP 와 매 traffic spike 가 매 매출 / engagement 에 매 직결"**. 매 1 초 LCP 개선 = Amazon $1B/yr (고전), Pinterest +15% conversion. 매 2026 stack — Next.js 15 + ISR + Edge / CDN + Image optim — 가 매 default playbook.
## 매 핵심
### 매 두 도메인 의 차이
- **이커머스**: 매 product page 의 매 LCP (image), 매 PDP 의 매 INP (variant select), 매 cart 의 매 freshness.
- **뉴스**: 매 article 의 매 LCP (cover image / headline), 매 publishing speed (ISR ≤ 60s), 매 ad / tracker 부하.
### 매 핵심 lever
- **ISR (Incremental Static Regeneration)**: build-time SSG + runtime revalidate.
- **Edge caching**: Vercel / Cloudflare / Fastly 의 매 stale-while-revalidate.
- **Image CDN**: Cloudinary / imgix / Next Image — 매 AVIF + responsive.
- **Streaming SSR**: RSC + Suspense → 매 TTFB 개선.
- **Third-party tax 최소화**: GTM / pixel — 매 Partytown 또는 server-side tracking.
### 매 응용
1. Shopify Hydrogen / Next Commerce.
2. NYT, WaPo, Bloomberg 식 article.
3. Medium / Substack 식 long-tail.
## 💻 패턴
### ISR (Next 15)
```tsx
// app/products/[id]/page.tsx
export const revalidate = 60; // 매 60초 ISR
export default async function Product({ params }) {
const product = await getProduct(params.id);
return <ProductView product={product} />;
}
export async function generateStaticParams() {
const top = await getTopProducts(1000); // 매 top 1000 prebuild
return top.map(p => ({ id: p.id }));
}
```
### On-demand revalidation
```tsx
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
export async function POST(req: Request) {
const { path, secret } = await req.json();
if (secret !== process.env.REVALIDATE_SECRET) return new Response("nope", { status: 401 });
revalidatePath(path);
return Response.json({ revalidated: true });
}
```
### Streaming SSR (RSC + Suspense)
```tsx
export default function ArticlePage({ params }) {
return (
<>
<ArticleHeader id={params.id} /> {/* 매 즉시 stream */}
<Suspense fallback={<CommentsSkeleton />}>
<Comments id={params.id} /> {/* 매 below-fold */}
</Suspense>
</>
);
}
```
### Edge runtime (latency 최소)
```tsx
export const runtime = "edge";
export default async function Page() {
const data = await fetch("https://api...", { next: { revalidate: 30 } });
return <View data={await data.json()} />;
}
```
### Image responsive + priority
```tsx
<Image
src={product.cover}
alt={product.name}
width={800} height={600}
sizes="(max-width: 768px) 100vw, 50vw"
priority={isAboveFold}
quality={80}
/>
```
### Partytown (3rd-party off main thread)
```tsx
import Script from "next/script";
<Script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXX"
strategy="worker" />
```
### Cache header (CDN-friendly)
```tsx
return new Response(html, {
headers: {
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=600",
},
});
```
## 매 결정 기준
| 페이지 타입 | 전략 |
|---|---|
| Product Detail (top seller) | SSG + ISR(60s) + Edge |
| Product Detail (long tail) | SSR streaming |
| Cart / Checkout | SSR (no cache, fresh) |
| News article | SSG + ISR(30s) + on-demand |
| Live ticker | RSC + WebSocket |
**기본값**: **Next 15 App Router + ISR + Edge runtime + Next/Image + Partytown**.
## 🔗 Graph
- 부모: [[Web Performance]] · [[Large_Frontend_Projects|Frontend Architecture]]
- 변형: [[ISR]] · [[Edge Computing]] · [[Streaming SSR]]
- 응용: [[Headless Commerce]]
- Adjacent: [[CDN]] · [[Core Web Vitals Optimization (INP, LCP 개선)|Core Web Vitals]] · [[SEO]]
## 🤖 LLM 활용
**언제**: 매 traffic-heavy content site, 매 PLP/PDP, 매 article-driven SEO.
**언제 X**: 매 internal admin, 매 auth-only SaaS.
## ❌ 안티패턴
- **Cart 까지 ISR**: 매 stale price → 매 trust 붕괴.
- **3rd-party 30+ scripts on main thread**: 매 INP 폭주.
- **Image 원본 그대로**: 매 5MB JPG → LCP 5초.
- **ISR revalidate 너무 짧음 (1s)**: 매 origin 부하.
## 🧪 검증 / 중복
- Verified (Vercel docs, web.dev case studies, Cloudflare Workers).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — content site tuning playbook |
@@ -0,0 +1,33 @@
---
id: wiki-2026-0508-프론트엔드-컴포넌트-설계
title: 프론트엔드 컴포넌트 설계
category: 10_Wiki/Topics
status: duplicate
canonical_id: frontend-component-design
duplicate_of: "[[Frontend Component Design]]"
aliases: []
source_trust_level: A
confidence_score: 0.9
verification_status: redirected
tags: [duplicate, frontend, component, design, architecture]
last_reinforced: 2026-05-10
github_commit: pending
---
# 프론트엔드 컴포넌트 설계
> **이 문서는 [[Frontend Component Design]] 의 중복본입니다.** Canonical 문서로 redirect.
## 핵심 요약
- 매 single-responsibility · 매 composition over inheritance · 매 controlled vs uncontrolled.
- 매 modern pattern: 매 headless UI (Radix/Ark) · 매 compound components · 매 slot prop · 매 Server/Client split.
- 매 design system 통합: 매 tokens · 매 Storybook · 매 visual regression (Chromatic).
## 🔗 Graph
- Adjacent: [[Design Systems]] · [[React Server Components]]
## 🕓 변경 이력
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | 중복 처리 — canonical 문서로 redirect |
@@ -0,0 +1,34 @@
---
id: wiki-2026-0508-하이드레이션-hydration
title: 하이드레이션 (Hydration)
category: 10_Wiki/Topics
status: duplicate
canonical_id: hydration
duplicate_of: "[[Hydration]]"
aliases: []
source_trust_level: A
confidence_score: 0.9
verification_status: redirected
tags: [duplicate, frontend, ssr, hydration]
last_reinforced: 2026-05-10
github_commit: pending
---
# 하이드레이션 (Hydration)
> **이 문서는 [[Hydration]] 의 중복본입니다.** Canonical 문서로 redirect.
## 핵심 요약
- 매 SSR/SSG로 전송된 정적 HTML에 client-side JS event handler / state 를 attach 하는 process.
- 매 React 18+ `hydrateRoot` · 매 selective hydration · 매 streaming SSR 의 partial hydration.
- 매 hydration mismatch 의 SSR/CSR markup 일치 의 강제.
## 🔗 Graph
- 부모: [[Hydration]] (canonical)
- Adjacent: [[Server-Side Rendering (SSR)]] · [[React Server Components]] · [[Streaming SSR]]
## 🕓 변경 이력
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | 중복 처리 — canonical 문서로 redirect |
@@ -0,0 +1,34 @@
---
id: wiki-2026-0508-확장-가능한-프론트엔드-아키텍처-scalable-front
title: 확장 가능한 프론트엔드 아키텍처(Scalable Frontend Architecture)
category: 10_Wiki/Topics
status: duplicate
canonical_id: scalable-frontend-architecture
duplicate_of: "[[Scalable Frontend Architecture]]"
aliases: []
source_trust_level: A
confidence_score: 0.9
verification_status: redirected
tags: [duplicate, frontend, architecture, micro-frontend]
last_reinforced: 2026-05-10
github_commit: pending
---
# 확장 가능한 프론트엔드 아키텍처(Scalable Frontend Architecture)
> **이 문서는 [[Scalable Frontend Architecture]] 의 중복본입니다.** Canonical 문서로 redirect.
## 핵심 요약
- 매 monorepo (Turborepo/Nx) · 매 module federation · 매 micro-frontend (Module Federation 2.0).
- 매 layered: 매 design system · 매 feature module · 매 app shell · 매 BFF (Backend-for-Frontend).
- 매 state 분리: 매 server state (TanStack Query) · 매 URL state · 매 client state (Zustand/Jotai).
## 🔗 Graph
- 부모: [[Scalable Frontend Architecture]] (canonical)
- Adjacent: [[Micro-Frontends]] · [[Module Federation]] · [[Monorepo (Turborepo / Nx)]]
## 🕓 변경 이력
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | 중복 처리 — canonical 문서로 redirect |
@@ -0,0 +1,33 @@
---
id: wiki-2026-0508-힙-스냅샷-heap-snapshots
title: 힙 스냅샷 (Heap Snapshots)
category: 10_Wiki/Topics
status: duplicate
canonical_id: heap-snapshots
duplicate_of: "[[Heap Snapshots]]"
aliases: []
source_trust_level: A
confidence_score: 0.9
verification_status: redirected
tags: [duplicate, frontend, debugging, memory, devtools]
last_reinforced: 2026-05-10
github_commit: pending
---
# 힙 스냅샷 (Heap Snapshots)
> **이 문서는 [[Heap Snapshots]] 의 중복본입니다.** Canonical 문서로 redirect.
## 핵심 요약
- 매 V8 heap 의 dump — 매 Chrome DevTools Memory tab · 매 retainer chain 의 inspect.
- 매 leak detection: 매 3-snapshot technique · 매 detached DOM · 매 closure-captured listener.
- 매 modern alternative: 매 `performance.measureUserAgentSpecificMemory()` · 매 Lighthouse memory audits.
## 🔗 Graph
- Adjacent: [[Memory Leak Detection]] · [[Chrome DevTools 메모리 프로파일링|Chrome DevTools]]
## 🕓 변경 이력
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | 중복 처리 — canonical 문서로 redirect |