이모지 아이콘을 SVG로 바꾸다가, 더 큰 원인을 발견해 함께 고쳤다.
■ 폰트: 이름만 부르고 파일은 안 받아오고 있었다
global.css에 font-family: "Pretendard"가 적혀 있었지만 @font-face도 CDN
링크도 없었다. 그래서 이 글꼴이 따로 깔린 사람 외에는 전원 맑은 고딕으로
보고 있었다 — 앱과 학습 문서 19개 전부. 에러가 안 나서 조용히 넘어간 케이스.
- PretendardStdVariable.woff2(285KB)를 public/fonts에 self-host
· CDN 의존 없음 → 외부 서비스가 죽어도, 오프라인에서도 안 깨진다
· variable font → 파일 하나가 굵기 45~920을 전부 담는다
· font-display: swap → 받는 동안 흰 화면(FOIT) 방지
- 전체판(2MB) 대신 Std판(상용 한글 2,350자)을 골랐다. 앱이 쓰는 한글
1,276자가 Std에 전부 있는지 브라우저에서 실측 확인했다(폴백 0자).
- 학습 문서 19개에도 같은 @font-face 적용
■ 아이콘: 이모지 95종 → Lucide 132개
이모지는 OS마다 다른 그림으로 렌더링되고, 굵기·색을 통제할 수 없고,
스크린리더가 "bust in silhouette"처럼 읽는다.
- 카탈로그의 icon: '📜'(문자) → Icon: ScrollText(컴포넌트)로 전환
- strokeWidth={1.5}로 굵기 통일, aria-hidden으로 장식임을 명시
- 개별 import만 사용(트리셰이킹) → gzip +14KB
- slug: null인 '개발 환경 설치 가이드' 항목이 일괄 변환에서 누락돼
Icon이 undefined가 될 뻔한 것을 정합성 검사(항목 수 vs Icon 수)로 잡았다
■ 토큰: 여백·글자 크기·굵기를 :root로 승격
--space-1~7(4의 배수), --text-xs~2xl, --weight-*, --radius-sm/lg/full.
값을 직접 박으면 다크 모드에서 깨지고 화면마다 미묘하게 어긋난다.
CSS 프레임워크는 도입하지 않았다 — 손으로 쓴 global.css와 충돌하고,
교재로서 "왜 이렇게 썼는지"가 사라지기 때문이다.
■ /design: 재료를 고르는 화면
색·글자·여백·모서리를 실제 CSS 변수로 렌더링(다크 모드 자동 반영) +
Lucide 아이콘 1,993개 검색·복사. lucide-react는 아이콘 하나를 이름 3개로
내보내(Rocket/RocketIcon/LucideRocket) 5,981개처럼 보이므로 별칭을 걸렀다.
아이콘 전체를 훑어야 해 무거운 화면이라 lazy로 분리했다
→ 첫 화면 번들은 1.9KB만 증가(575.57 → 577.44KB).
검증: ESLint 통과, Vitest 30개 통과, 프로덕션 빌드 성공, 콘솔 에러 0.
브라우저에서 수습 7카테고리 + 초급~특급 4과정 = 11개 화면 전수 확인
(각 12카드/12아이콘/이모지 0).
문서: DESIGN.md(폰트·토큰·비용), ICONS.md(규약·대응표 132행)
6.3 KiB
미림 앱 (mirim-app) — 어썸데브 학습 플랫폼
이 저장소는 여러분(수습생)의 것입니다. 미림마이스터고 수습생 4명이 직접 보고, 배우고, 고치는 학습용 웹 앱입니다. 처음에는 문서를 읽는 곳이지만, 5주차부터는 여러분이 직접 기능을 붙이고 버그를 고치는 실전 연습장이 됩니다. 코드를 읽는 법이 궁금하면 STUDY.md부터 열어 보세요. 화면을 만들 일이 생기면 DESIGN.md(색·글자·여백·폰트)와 ICONS.md(아이콘)를 먼저 보세요. 앱에서
/design을 열면 재료를 눈으로 보고 클릭해서 복사할 수 있습니다.
무엇을 하는 앱인가?
- 수습 기간(8주, 9월~10월) 동안 필요한 학습 문서(가이드·커리큘럼·수업자료·과제·실습가이드)를 한곳에서 봅니다.
- 학습 센터에서 수습 트랙 강좌 83개(컴퓨터 기초~앱 개발)와 수준별 과정(초급·중급·고급·특급) 48개를 순서대로 배우고, 각 코스 끝에서 퀴즈로 확인합니다.
- 코딩 문제 25개(JavaScript·SQL·Java)를 브라우저에서 직접 풀고 자동 채점받습니다.
- 주차별 체크리스트로 오늘 할 일을 확인하고, 과제를 제출하면 멘토가 피드백을 남깁니다.
- 멘토는 학생별 진도·제출물·인적사항을 한 화면에서 관리합니다.
기술 스택
| 영역 | 기술 | 비고 |
|---|---|---|
| 프론트엔드 | React + Vite | frontend/ — 브라우저에서 돌아가는 화면 |
| 백엔드 | Spring Boot 3 + Java 21 | backend/ — API 서버 |
| 데이터베이스 | PostgreSQL 17 | Docker 컨테이너로 실행 |
| 인프라 | Docker Compose | 로컬에서 DB를 띄우는 용도 |
폴더 구조
mirim-app/
├── README.md ← 지금 읽는 파일 (프로젝트 소개·실행법)
├── STUDY.md ← 수습생용 "이 코드로 배우는 법"
├── docker-compose.yml ← PostgreSQL을 띄우는 설정
├── backend/ ← Spring Boot API 서버
│ ├── pom.xml ← 자바 의존성(라이브러리) 목록
│ └── src/main/
│ ├── java/dev/awesomedev/mirim/
│ │ ├── MirimApplication.java ← 앱 시작점
│ │ ├── domain/ ← 엔티티(DB 테이블과 짝이 되는 클래스)
│ │ ├── repository/ ← DB를 읽고 쓰는 계층
│ │ ├── service/ ← 비즈니스 로직(규칙) 계층
│ │ ├── web/ ← HTTP 요청을 받는 계층(컨트롤러들)
│ │ └── config/ ← 보안·시드 등 설정
│ └── resources/
│ ├── application.yml ← 서버 설정(DB 접속 정보 등)
│ └── seed/ ← 초기 데이터 JSON (문서·체크리스트·과제·퀴즈·코딩문제)
└── frontend/ ← React 화면
├── package.json ← JS 의존성 목록
├── index.html ← 브라우저가 처음 여는 파일
├── public/docs/ ← 학습 문서 HTML 19개 (앱이 iframe으로 보여줌)
└── src/
├── pages/ ← 화면 단위 컴포넌트 (로그인, 대시보드, 문서, 과제…)
│ └── courses/ ← 학습 센터의 강좌 페이지들 (코스 하나당 파일 하나)
├── components/ ← 여러 화면에서 재사용하는 조각 컴포넌트
└── api/ ← 백엔드 API를 부르는 함수 모음
로컬 실행법 (3단계)
0. 준비물
- Docker Desktop
- Java 21 (JDK)
- Node.js 20 이상
1. 데이터베이스 켜기
# 프로젝트 루트(mirim-app/)에서
docker compose up -d
PostgreSQL이 컨테이너로 뜹니다. docker ps로 확인하세요.
2. 백엔드 켜기
cd backend
./mvnw spring-boot:run # mvnw가 안 되면: mvn spring-boot:run
http://localhost:8080 에서 API 서버가 돌아갑니다. 첫 실행 시 시드 데이터(문서·체크리스트·과제·계정)가 자동으로 들어갑니다.
3. 프론트엔드 켜기
cd frontend
npm install # 최초 1회
npm run dev
브라우저에서 http://localhost:5173 을 열면 로그인 화면이 보입니다.
초기 계정
시드로는 멘토 계정(mentor1) 하나만 만들어집니다. 학생은 첫날 각자 회원가입으로 등록해요 (개인정보를 본인이 입력하는 게 정확하고, "받은 비밀번호를 바꾸는 것이 첫 일"을 배우니까요).
| 아이디 | 비밀번호 | 역할 |
|---|---|---|
| mentor1 | MENTOR_INITIAL_PASSWORD 환경변수, 없으면 부팅 로그에 1회 출력되는 랜덤값 |
멘토 (MENTOR) |
⚠️ 소스에 박힌 기본 비밀번호는 없습니다 — 첫 부팅 로그에서 멘토 비밀번호를 확인하고, 로그인 후 즉시 변경하세요.
환경변수
기본값이 로컬 개발에 맞춰져 있어서, 그냥 실행하면 대부분 그대로 동작합니다.
| 변수 | 기본값 | 설명 |
|---|---|---|
DB_HOST |
localhost |
PostgreSQL 호스트 |
DB_PORT |
5432 |
PostgreSQL 포트 |
DB_NAME |
mirim |
데이터베이스 이름 |
DB_USER |
mirim |
DB 사용자 |
DB_PASSWORD |
(docker-compose.yml 참고) | DB 비밀번호 |
SERVER_PORT |
8080 |
백엔드 포트 |
🔒 시크릿(비밀번호·키)은 절대 커밋하지 마세요. 로컬 기본값은 학습 편의를 위한 것이고, 실제 배포용 비밀번호·키는
.env같은 별도 파일에 두고.gitignore에 등록합니다. "일단 코드에 박아 두고 나중에 지우지"는 통하지 않습니다 — git 이력에 영원히 남습니다.
자주 겪는 문제
- 백엔드가 DB에 못 붙어요 →
docker compose up -d를 먼저 했는지,docker ps에 postgres가 있는지 확인. - 5173 화면은 뜨는데 로그인이 안 돼요 → 백엔드(8080)가 켜져 있는지 확인. 프론트는 API를 백엔드로 보냅니다.
- 포트가 이미 사용 중이래요 → 이전에 켜 둔 서버가 남아 있을 수 있어요. 터미널을 확인하고 종료하세요.
AWESOMEDEV (어썸데브) · 수습 온보딩 프로그램 학습용 저장소