정적 에셋 관리
디렉토리 구조
static/
├── css/
│ ├── tokens.css # 디자인 토큰 (CSS 변수)
│ ├── reset.css # CSS 리셋
│ ├── style.css # 사이트 공통 스타일 (@import 조합)
│ ├── layout.css # 레이아웃 (grid, flex)
│ ├── header.css # 헤더 컴포넌트
│ ├── components.css # 공통 컴포넌트 (버튼, 카드 등)
│ ├── admin.css # 어드민 전용 스타일
│ ├── notes.css # 노트 페이지 전용
│ ├── resume.css # 이력서 페이지 전용
│ └── guide.css # 개발 가이드 전용
└── js/
├── data.js # 정적 데이터
└── guide.js # 개발 가이드 JS
디자인 토큰
/* tokens.css */
:root {
--bg: #F8F7F4;
--white: #FFFFFF;
--text-primary: #1A1917;
--text-secondary: #6B6965;
--text-muted: #9B9894;
--border: #E4E2DD;
--border-light: #EEECEA;
--accent: oklch(0.52 0.14 258);
--accent-bg: oklch(0.95 0.04 258);
--tag-bg: #F0EFEB;
--max-w: 1100px;
--max-w-resume: 900px;
}
색상 사용 규칙
| 토큰 | 사용 용도 |
|---|---|
--bg | 페이지 배경 |
--white | 카드, 모달 배경 |
--text-primary | 제목, 주요 텍스트 |
--text-secondary | 부제목, 보조 텍스트 |
--text-muted | 힌트, 비활성 텍스트 |
--border | 기본 경계선 |
--border-light | 연한 구분선 |
--accent | 링크, 버튼, 강조 |
--accent-bg | 강조 배경 |
--tag-bg | 태그 배경 |
하드코딩 금지
/* 잘못된 예 */
color: #1A1917;
background: oklch(0.52 0.14 258);
/* 올바른 예 */
color: var(--text-primary);
background: var(--accent);
CSS 작성 규칙
파일 구성
- 파일당 하나의 컴포넌트/페이지 담당
style.css에서@import로 조합- 전역 선택자(*, body) 수정 →
reset.css에서만
선택자 규칙
/* BEM-like 네이밍 */
.news-card {} /* 블록 */
.news-card-title {} /* 요소 */
.news-card--featured {} /* 변형 */
/* 상태 클래스 */
.nav-link.active {}
.btn:disabled {}
반응형 브레이크포인트
/* 모바일 우선 */
.container { width: 100%; }
@media (min-width: 768px) {
.container { max-width: var(--max-w); }
}
JavaScript 작성 규칙
기본 원칙
// eval() 절대 금지
// innerHTML 직접 할당 금지 → textContent 또는 DOM API 사용
// 잘못된 예
element.innerHTML = userInput;
// 올바른 예
element.textContent = userInput;
// 또는 안전한 DOM 조작
const span = document.createElement('span');
span.textContent = userInput;
element.appendChild(span);
이벤트 리스너
// 이벤트 위임 패턴 (성능 최적화)
document.querySelector('.list').addEventListener('click', (e) => {
const btn = e.target.closest('.delete-btn');
if (!btn) return;
handleDelete(btn.dataset.id);
});
API 호출
async function fetchData(url, body) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
외부 CDN 보안
외부 CDN 사용 시 SRI(Subresource Integrity) 해시 필수:
<link rel="stylesheet"
href="https://cdn.example.com/lib.css"
integrity="sha384-..."
crossorigin="anonymous">
<script src="https://cdn.example.com/lib.js"
integrity="sha384-..."
crossorigin="anonymous"></script>
SRI 해시 생성: srihash.org
이미지 최적화
<!-- WebP 우선, PNG 폴백 -->
<picture>
<source srcset="/img/photo.webp" type="image/webp">
<img src="/img/photo.png" alt="설명" loading="lazy" width="800" height="600">
</picture>
<!-- 중요한 이미지 (LCP) -->
<img src="/img/hero.png" alt="..." loading="eager" fetchpriority="high">