feat: 카테고리당 12강좌 체제 완성 — 총 72강좌, 학습 순서 정렬 + 순번 표시

- 신규 16강좌: GPU·메인보드/파워·대역폭·VPN/프록시·캐시/NoSQL·CI/CD·테스트·AI도구·
  와이어프레임·레퍼런스 + 부록 6종(개발자도구·터미널·Gitea·캡처·노션·단축키)
- 카탈로그를 배우는 순서대로 재배열 (배열 순서 = 학습 순서)
- 허브 카드에 순번(01~12) 표시, 카테고리당 12강좌 균일화

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
AWESOMEDEV 2026-07-16 10:21:04 +09:00
parent 96f92eceba
commit ff14183f03
18 changed files with 6097 additions and 30 deletions

View File

@ -1,10 +1,10 @@
// : " " ()
// . .
// = . (01, 02...) .
//
// 1: 55 App.jsx .
// 1: 70 App.jsx .
// "() (map)" .
// 2: lazy( ) .
// 55 .
// (dist/assets) (chunk) .
import { lazy } from 'react';
@ -19,9 +19,7 @@ const HtmlCssPage = lazy(() => import('./pages/HtmlCssPage'));
const JavascriptPage = lazy(() => import('./pages/JavascriptPage'));
const UiUxPage = lazy(() => import('./pages/UiUxPage'));
const FigmaPage = lazy(() => import('./pages/FigmaPage'));
// (pages/courses/)
//
// (pages/courses/)
const OsPage = lazy(() => import('./pages/courses/OsPage'));
const CpuDeepPage = lazy(() => import('./pages/courses/CpuDeepPage'));
const MemoryStoragePage = lazy(() => import('./pages/courses/MemoryStoragePage'));
@ -30,7 +28,8 @@ const BinaryPage = lazy(() => import('./pages/courses/BinaryPage'));
const TroubleshootingPage = lazy(() => import('./pages/courses/TroubleshootingPage'));
const PeripheralsPage = lazy(() => import('./pages/courses/PeripheralsPage'));
const HistoryPage = lazy(() => import('./pages/courses/HistoryPage'));
//
const GpuPage = lazy(() => import('./pages/courses/GpuPage'));
const MainboardPowerPage = lazy(() => import('./pages/courses/MainboardPowerPage'));
const IpDeepPage = lazy(() => import('./pages/courses/IpDeepPage'));
const HomeNetworkPage = lazy(() => import('./pages/courses/HomeNetworkPage'));
const DnsDeepPage = lazy(() => import('./pages/courses/DnsDeepPage'));
@ -40,7 +39,8 @@ const NetToolsPage = lazy(() => import('./pages/courses/NetToolsPage'));
const WifiSecurityPage = lazy(() => import('./pages/courses/WifiSecurityPage'));
const FirewallPage = lazy(() => import('./pages/courses/FirewallPage'));
const CloudNetworkPage = lazy(() => import('./pages/courses/CloudNetworkPage'));
//
const BandwidthPage = lazy(() => import('./pages/courses/BandwidthPage'));
const VpnProxyPage = lazy(() => import('./pages/courses/VpnProxyPage'));
const LinuxAdvancedPage = lazy(() => import('./pages/courses/LinuxAdvancedPage'));
const ServerAnatomyPage = lazy(() => import('./pages/courses/ServerAnatomyPage'));
const AwsIntroPage = lazy(() => import('./pages/courses/AwsIntroPage'));
@ -49,7 +49,8 @@ const SqlIntermediatePage = lazy(() => import('./pages/courses/SqlIntermediatePa
const DataModelingPage = lazy(() => import('./pages/courses/DataModelingPage'));
const ApiPage = lazy(() => import('./pages/courses/ApiPage'));
const OpsBasicsPage = lazy(() => import('./pages/courses/OpsBasicsPage'));
//
const CacheNosqlPage = lazy(() => import('./pages/courses/CacheNosqlPage'));
const CicdPage = lazy(() => import('./pages/courses/CicdPage'));
const GitDeepPage = lazy(() => import('./pages/courses/GitDeepPage'));
const ReactIntroPage = lazy(() => import('./pages/courses/ReactIntroPage'));
const JavaBasicsPage = lazy(() => import('./pages/courses/JavaBasicsPage'));
@ -57,7 +58,8 @@ const SpringIntroPage = lazy(() => import('./pages/courses/SpringIntroPage'));
const DataStructuresPage = lazy(() => import('./pages/courses/DataStructuresPage'));
const DebuggingPage = lazy(() => import('./pages/courses/DebuggingPage'));
const CleanCodePage = lazy(() => import('./pages/courses/CleanCodePage'));
//
const TestingPage = lazy(() => import('./pages/courses/TestingPage'));
const AiToolsPage = lazy(() => import('./pages/courses/AiToolsPage'));
const ColorPage = lazy(() => import('./pages/courses/ColorPage'));
const TypographyPage = lazy(() => import('./pages/courses/TypographyPage'));
const LayoutPage = lazy(() => import('./pages/courses/LayoutPage'));
@ -66,30 +68,38 @@ const DesignSystemPage = lazy(() => import('./pages/courses/DesignSystemPage'));
const MobileDesignPage = lazy(() => import('./pages/courses/MobileDesignPage'));
const UserResearchPage = lazy(() => import('./pages/courses/UserResearchPage'));
const PortfolioPage = lazy(() => import('./pages/courses/PortfolioPage'));
// :
const WireframePage = lazy(() => import('./pages/courses/WireframePage'));
const DesignRefsPage = lazy(() => import('./pages/courses/DesignRefsPage'));
const ToolDockerPage = lazy(() => import('./pages/courses/ToolDockerPage'));
const ToolDbeaverPage = lazy(() => import('./pages/courses/ToolDbeaverPage'));
const ToolVscodePage = lazy(() => import('./pages/courses/ToolVscodePage'));
const ToolIntellijPage = lazy(() => import('./pages/courses/ToolIntellijPage'));
const ToolPostmanPage = lazy(() => import('./pages/courses/ToolPostmanPage'));
const ToolDevtoolsPage = lazy(() => import('./pages/courses/ToolDevtoolsPage'));
const ToolTerminalPage = lazy(() => import('./pages/courses/ToolTerminalPage'));
const ToolGiteaPage = lazy(() => import('./pages/courses/ToolGiteaPage'));
const ToolCapturePage = lazy(() => import('./pages/courses/ToolCapturePage'));
const ToolNotionPage = lazy(() => import('./pages/courses/ToolNotionPage'));
const ToolShortcutsPage = lazy(() => import('./pages/courses/ToolShortcutsPage'));
// . slug = /learn/<slug> .
// external: true ( ).
// . slug = /learn/<slug> . = ( ).
export const COURSE_CATEGORIES = [
{
category: '컴퓨터 기초',
desc: '소프트웨어 이전에, 기계 그 자체를 이해해요.',
items: [
{ slug: 'hardware', icon: '💻', title: '컴퓨터의 구성', desc: 'CPU·RAM·스토리지·메인보드 — 각 부품의 역할과 스펙표 읽는 법.', level: '입문', Component: HardwarePage },
{ slug: 'assembly', icon: '🔧', title: '데스크탑 조립하기', desc: '부품 고르기부터 9단계 조립 순서, 초보 실수까지.', level: '입문', Component: AssemblyPage },
{ slug: 'os', icon: '🪟', title: '운영체제의 이해', desc: '윈도우·맥·리눅스가 하는 일 — 자원 관리부터 멀티태스킹까지.', level: '입문', Component: OsPage },
{ slug: 'history', icon: '📜', title: '컴퓨터의 역사', desc: '에니악에서 AI까지 — 우리가 어디쯤 서 있는지부터.', level: '교양', Component: HistoryPage },
{ slug: 'hardware', icon: '💻', title: '컴퓨터의 구성', desc: 'CPU·RAM·스토리지·메인보드 — 부품의 역할과 스펙 읽기.', level: '입문', Component: HardwarePage },
{ slug: 'binary', icon: '🔢', title: '2진수와 데이터 표현', desc: '문자·색·이미지가 전부 숫자인 이유.', level: '입문', Component: BinaryPage },
{ slug: 'cpu-deep', icon: '🧠', title: 'CPU 깊이 보기', desc: '명령어 사이클·코어·캐시·발열 — 두뇌의 작동 원리.', level: '심화', Component: CpuDeepPage },
{ slug: 'memory-storage', icon: '💾', title: '메모리와 저장장치 깊이 보기', desc: 'RAM·SSD의 내부, 스왑과 느려짐의 연쇄.', level: '심화', Component: MemoryStoragePage },
{ slug: 'gpu', icon: '🎮', title: '그래픽카드와 GPU의 세계', desc: '화면 그리기 전담 — AI 시대에 GPU가 금값인 이유.', level: '심화', Component: GpuPage },
{ slug: 'mainboard-power', icon: '🔋', title: '메인보드와 파워 깊이 보기', desc: '모든 부품이 만나는 도로망, 그리고 전원의 심장.', level: '심화', Component: MainboardPowerPage },
{ slug: 'assembly', icon: '🔧', title: '데스크탑 조립하기', desc: '배운 부품들을 내 손으로 — 9단계 조립 순서.', level: '실습', Component: AssemblyPage },
{ slug: 'os', icon: '🪟', title: '운영체제의 이해', desc: '윈도우·맥·리눅스가 하는 일 — 자원 관리부터 멀티태스킹까지.', level: '입문', Component: OsPage },
{ slug: 'files', icon: '📁', title: '파일과 폴더의 원리', desc: '확장자의 진실, 경로, 휴지통과 완전삭제.', level: '입문', Component: FilesPage },
{ slug: 'binary', icon: '🔢', title: '2진수와 데이터 표현', desc: '문자·색·이미지가 전부 숫자인 이유.', level: '입문', Component: BinaryPage },
{ slug: 'troubleshooting', icon: '🩺', title: '컴퓨터 문제 해결', desc: '느려질 때 점검 순서, 백업 3-2-1, 검색의 기술.', level: '실습', Component: TroubleshootingPage },
{ slug: 'peripherals', icon: '🖥️', title: '노트북과 주변기기 고르기', desc: '노트북·모니터·키보드 스펙 읽기 — 개발자 관점.', level: '입문', Component: PeripheralsPage },
{ slug: 'history', icon: '📜', title: '컴퓨터의 역사', desc: '에니악에서 AI까지 — 지금 우리 개발에 남긴 것들.', level: '교양', Component: HistoryPage },
{ slug: 'troubleshooting', icon: '🩺', title: '컴퓨터 문제 해결', desc: '느려질 때 점검 순서, 백업 3-2-1, 검색의 기술.', level: '실습', Component: TroubleshootingPage },
],
},
{
@ -97,13 +107,15 @@ export const COURSE_CATEGORIES = [
desc: '랜선 한 가닥에서 클라우드까지.',
items: [
{ slug: 'network', icon: '🌐', title: '네트워크의 이해', desc: '랜선(UTP) 구조 → 인터넷의 구조 → DNS·TCP/IP 한 흐름.', level: '입문', Component: NetworkPage },
{ slug: 'bandwidth', icon: '🚗', title: '네트워크 속도와 대역폭', desc: 'Mbps의 진실 — 다운로드가 생각보다 느린 이유.', level: '입문', Component: BandwidthPage },
{ slug: 'ip-deep', icon: '🔍', title: 'IP 주소 깊이 보기', desc: '사설 대역·서브넷·DHCP·IPv6 — 주소 체계의 모든 것.', level: '심화', Component: IpDeepPage },
{ slug: 'home-network', icon: '📡', title: '공유기와 홈 네트워크 실전', desc: '관리자 페이지, WiFi 채널, 포트포워딩까지.', level: '실습', Component: HomeNetworkPage },
{ slug: 'dns-deep', icon: '📖', title: 'DNS 깊이 보기', desc: '재귀 조회의 여행, 레코드 타입, TTL과 전파.', level: '심화', Component: DnsDeepPage },
{ slug: 'http', icon: '📨', title: 'HTTP와 HTTPS', desc: '메서드·상태코드·쿠키(우리 로그인!)·TLS 인증서.', level: '입문', Component: HttpPage },
{ slug: 'tcp-udp', icon: '🤝', title: 'TCP와 UDP', desc: '3-way 핸드셰이크, 패킷, 왜 게임은 UDP인가.', level: '심화', Component: TcpUdpPage },
{ slug: 'http', icon: '📨', title: 'HTTP와 HTTPS', desc: '메서드·상태코드·쿠키(우리 로그인!)·TLS 인증서.', level: '입문', Component: HttpPage },
{ slug: 'net-tools', icon: '🧰', title: '네트워크 명령어 실전', desc: 'ping·tracert·nslookup·curl — 진단의 무기들.', level: '실습', Component: NetToolsPage },
{ slug: 'wifi-security', icon: '🔐', title: '무선 네트워크와 보안', desc: '공용 와이파이의 위험, 피싱 구별, 2단계 인증.', level: '입문', Component: WifiSecurityPage },
{ slug: 'vpn-proxy', icon: '🚇', title: 'VPN과 프록시', desc: '암호화 터널과 대리인 — 우리 Caddy가 실물 교재.', level: '심화', Component: VpnProxyPage },
{ slug: 'firewall', icon: '🛡️', title: '방화벽과 보안 기초', desc: '포트의 여닫음, 우리 AWS 보안그룹 실례, 최소 권한.', level: '입문', Component: FirewallPage },
{ slug: 'cloud-network', icon: '☁️', title: '클라우드 네트워크 입문', desc: '리전·VPC·탄력적 IP — 우리 플랫폼의 실제 구성도.', level: '심화', Component: CloudNetworkPage },
],
@ -112,15 +124,17 @@ export const COURSE_CATEGORIES = [
category: '서버와 데이터',
desc: '백엔드 개발자가 매일 쓰는 도구들.',
items: [
{ slug: 'server-anatomy', icon: '🏗️', title: '서버란 무엇인가', desc: '웹서버·WAS·DB 3계층 — 우리 구성 그대로 해부.', level: '입문', Component: ServerAnatomyPage },
{ slug: 'linux', icon: '🐧', title: '리눅스 / 터미널 기초', desc: '터미널 명령어부터 SSH 서버 접속까지.', level: '입문', Component: LinuxPage },
{ slug: 'linux-advanced', icon: '⚙️', title: '리눅스 심화', desc: '셸 스크립트, 크론, 파이프 — 자동화의 시작.', level: '심화', Component: LinuxAdvancedPage },
{ slug: 'server-anatomy', icon: '🏗️', title: '서버란 무엇인가', desc: '웹서버·WAS·DB 3계층 — 우리 구성 그대로 해부.', level: '입문', Component: ServerAnatomyPage },
{ slug: 'aws-intro', icon: '🌩️', title: '클라우드와 AWS 입문', desc: 'EC2·요금·리전 — 우리 인프라가 교재.', level: '입문', Component: AwsIntroPage },
{ slug: 'docker-intro', icon: '🐳', title: '도커 입문', desc: '컨테이너 개념과 우리 docker-compose.yml 한 줄씩 해부.', level: '입문', Component: DockerIntroPage },
{ slug: 'aws-intro', icon: '🌩️', title: '클라우드와 AWS 입문', desc: 'EC2·요금·리전 — 우리 인프라가 교재.', level: '입문', Component: AwsIntroPage },
{ slug: 'sql', icon: '🗄️', title: 'SQL 입문', desc: '우리 플랫폼의 진짜 테이블로 SELECT부터 JOIN까지.', level: '입문', Component: SqlPage },
{ slug: 'sql-intermediate', icon: '📊', title: 'SQL 중급', desc: 'GROUP BY·집계·인덱스 — 데이터에서 답을 꺼내기.', level: '심화', Component: SqlIntermediatePage },
{ slug: 'data-modeling', icon: '🗂️', title: '데이터 모델링 기초', desc: 'ERD·키·정규화 — 테이블 설계는 정리정돈.', level: '심화', Component: DataModelingPage },
{ slug: 'cache-nosql', icon: '⚡', title: '캐시와 NoSQL 맛보기', desc: '자주 쓰는 건 책상 위에 — Redis가 빠른 이유.', level: '심화', Component: CacheNosqlPage },
{ slug: 'api', icon: '🔌', title: 'API의 이해', desc: 'REST·JSON — 이미 다 써본 우리 API로 배우기.', level: '입문', Component: ApiPage },
{ slug: 'cicd', icon: '🔁', title: 'CI/CD와 배포 자동화 입문', desc: '푸시하면 알아서 검사·배포 — 파이프라인의 세계.', level: '심화', Component: CicdPage },
{ slug: 'ops-basics', icon: '🚨', title: '운영과 장애 대응 기초', desc: '장애의 신호, 로그 읽기, 재시작의 정석.', level: '실습', Component: OpsBasicsPage },
],
},
@ -131,13 +145,15 @@ export const COURSE_CATEGORIES = [
{ slug: 'coding', icon: '⌨️', title: '코딩 기초', desc: '변수·함수·조건·반복을 이 플랫폼의 실제 코드로.', level: '입문', Component: CodingBasicsPage },
{ slug: 'htmlcss', icon: '🧱', title: 'HTML / CSS 기초', desc: '웹의 뼈대와 옷 — 박스 모델·플렉스를 우리 스타일로.', level: '입문', Component: HtmlCssPage },
{ slug: 'javascript', icon: '⚡', title: '자바스크립트 기초', desc: 'F12 콘솔이 실습장 — 변수에서 fetch까지.', level: '입문', Component: JavascriptPage },
{ slug: 'git-deep', icon: '🌳', title: 'Git 깊이 보기', desc: '브랜치·충돌 해결·되돌리기 3형제·PR 매너.', level: '심화', Component: GitDeepPage },
{ slug: 'data-structures', icon: '🧺', title: '자료구조 맛보기', desc: '배열·스택·큐·해시 — 그릇마다 쓰임이 다르다.', level: '입문', Component: DataStructuresPage },
{ slug: 'react-intro', icon: '⚛️', title: 'React 입문', desc: '컴포넌트·props·state — 우리 프론트가 교재.', level: '입문', Component: ReactIntroPage },
{ slug: 'java-basics', icon: '☕', title: 'Java 기초', desc: '타입·클래스·객체 — Spring으로 가는 첫걸음.', level: '입문', Component: JavaBasicsPage },
{ slug: 'spring-intro', icon: '🍃', title: 'Spring Boot 입문', desc: 'DI·어노테이션·계층 구조 — 우리 백엔드 여행.', level: '심화', Component: SpringIntroPage },
{ slug: 'data-structures', icon: '🧺', title: '자료구조 맛보기', desc: '배열·스택·큐·해시 — 그릇마다 쓰임이 다르다.', level: '입문', Component: DataStructuresPage },
{ slug: 'git-deep', icon: '🌳', title: 'Git 깊이 보기', desc: '브랜치·충돌 해결·되돌리기 3형제·PR 매너.', level: '심화', Component: GitDeepPage },
{ slug: 'debugging', icon: '🐞', title: '디버깅과 에러 읽는 법', desc: '스택트레이스·브레이크포인트 — 에러는 단서다.', level: '실습', Component: DebuggingPage },
{ slug: 'testing', icon: '🧪', title: '테스트 코드 입문', desc: '미래의 나를 지키는 보험 — 우리 테스트가 교재.', level: '심화', Component: TestingPage },
{ slug: 'clean-code', icon: '✨', title: '클린 코드와 코드리뷰', desc: '이름 짓기·작은 함수·리뷰 매너.', level: '심화', Component: CleanCodePage },
{ slug: 'ai-tools', icon: '🤖', title: 'AI 도구와 함께 코딩하기', desc: '잘 시키고, 검증하고, 배움에 쓰는 법 — 보안 수칙까지.', level: '실습', Component: AiToolsPage },
],
},
{
@ -149,10 +165,12 @@ export const COURSE_CATEGORIES = [
{ slug: 'typography', icon: '🔤', title: '타이포그래피', desc: '폰트·크기 위계·행간 — 가독성의 과학.', level: '입문', Component: TypographyPage },
{ slug: 'layout', icon: '📐', title: '레이아웃과 그리드', desc: '8pt 그리드·정렬·여백 — 보이지 않는 뼈대.', level: '입문', Component: LayoutPage },
{ slug: 'icons-images', icon: '🖼️', title: '아이콘과 이미지', desc: 'SVG vs PNG, 해상도, 그리고 저작권!', level: '입문', Component: IconsImagesPage },
{ slug: 'wireframe', icon: '✏️', title: '와이어프레임 기초', desc: '색 없이 구조만 — 빨리 그리고 빨리 버리는 설계.', level: '실습', Component: WireframePage },
{ slug: 'figma', icon: '🖌️', title: '피그마와 디자인 툴', desc: '프레임·오토레이아웃·컴포넌트·핸드오프.', level: '실습', Component: FigmaPage },
{ slug: 'design-system', icon: '🧩', title: '디자인 시스템 입문', desc: '토큰과 컴포넌트 — 우리 global.css가 실물.', level: '심화', Component: DesignSystemPage },
{ slug: 'mobile-design', icon: '📱', title: '모바일 디자인', desc: '엄지의 법칙, 터치 타깃, 웹 vs 앱.', level: '입문', Component: MobileDesignPage },
{ slug: 'user-research', icon: '🔬', title: '사용자 리서치 기초', desc: '인터뷰·관찰·사용성 테스트 — 감이 아니라 근거로.', level: '심화', Component: UserResearchPage },
{ slug: 'figma', icon: '🖌️', title: '피그마와 디자인 툴', desc: '프레임·오토레이아웃·컴포넌트·핸드오프.', level: '실습', Component: FigmaPage },
{ slug: 'design-refs', icon: '🗃️', title: '레퍼런스와 무드보드', desc: '많이 본 사람이 잘 만든다 — 수집과 분석의 루틴.', level: '실습', Component: DesignRefsPage },
{ slug: 'portfolio', icon: '💼', title: '포트폴리오 만들기', desc: '수습 8주가 곧 재료 — 과정을 이야기로.', level: '실습', Component: PortfolioPage },
],
},
@ -161,11 +179,17 @@ export const COURSE_CATEGORIES = [
desc: '손에 익으면 두 배 빨라지는 도구들.',
items: [
{ slug: null, external: '/setup', icon: '🛠️', title: '개발 환경 설치 가이드', desc: 'JDK·PATH·Node·Git·IntelliJ — 환경 구축 9단계.', level: '실습' },
{ slug: 'tool-docker', icon: '🐳', title: 'Docker Desktop 사용법', desc: '대시보드 읽기, 컨테이너 GUI 관리, 용량 정리.', level: '실습', Component: ToolDockerPage },
{ slug: 'tool-dbeaver', icon: '🦫', title: 'DBeaver로 DB 들여다보기', desc: '우리 DB 연결, ERD 자동 생성, GUI로 쿼리.', level: '실습', Component: ToolDbeaverPage },
{ slug: 'tool-shortcuts', icon: '⌨️', title: '윈도우 단축키 마스터', desc: '마우스를 버릴수록 빨라진다 — 필수 30개.', level: '실습', Component: ToolShortcutsPage },
{ slug: 'tool-terminal', icon: '🖤', title: '윈도우 터미널과 PowerShell', desc: 'cmd·PowerShell·Git Bash 삼형제 정리.', level: '실습', Component: ToolTerminalPage },
{ slug: 'tool-vscode', icon: '📝', title: 'VS Code 200% 활용', desc: '단축키 15개, 확장 추천, 검색의 기술.', level: '실습', Component: ToolVscodePage },
{ slug: 'tool-intellij', icon: '💡', title: 'IntelliJ 꿀팁 모음', desc: 'Shift 두 번, Alt+Enter, 로컬 히스토리의 구원.', level: '실습', Component: ToolIntellijPage },
{ slug: 'tool-devtools', icon: '🔎', title: '크롬 개발자도구', desc: 'F12의 세계 — Elements·Console·Network 투어.', level: '실습', Component: ToolDevtoolsPage },
{ slug: 'tool-gitea', icon: '🍵', title: 'Gitea 200% 활용', desc: '우리 Git 서버 — 이슈·PR·리뷰 화면 사용법.', level: '실습', Component: ToolGiteaPage },
{ slug: 'tool-docker', icon: '🐳', title: 'Docker Desktop 사용법', desc: '대시보드 읽기, 컨테이너 GUI 관리, 용량 정리.', level: '실습', Component: ToolDockerPage },
{ slug: 'tool-dbeaver', icon: '🦫', title: 'DBeaver로 DB 들여다보기', desc: '우리 DB 연결, ERD 자동 생성, GUI로 쿼리.', level: '실습', Component: ToolDbeaverPage },
{ slug: 'tool-postman', icon: '🚀', title: 'Postman으로 API 테스트', desc: '화면 없이 API를 직접 두드리는 법.', level: '실습', Component: ToolPostmanPage },
{ slug: 'tool-capture', icon: '📸', title: '캡처와 화면 녹화', desc: 'PR·버그 리포트의 언어 — Win+Shift+S부터.', level: '실습', Component: ToolCapturePage },
{ slug: 'tool-notion', icon: '🗒️', title: '노션으로 기록하기', desc: 'TIL·트러블슈팅 — 검색되는 기록이 자산.', level: '실습', Component: ToolNotionPage },
],
},
];

View File

@ -15,9 +15,9 @@ export default function LearnHubPage() {
{ALL_COURSES.length + 1} 강좌, 아침 수업(CS 커리큘럼) 짝을 이루는 자율 학습 코너예요.
</p>
<div className="chip-row">
<span className="chip">추천 순서: 컴퓨터 네트워크 서버 소프트웨어 디자인</span>
<span className="chip">카드의 번호(01, 02) 배우는 순서예요</span>
<span className="chip">카테고리당 12강좌</span>
<span className="chip">강좌당 40~90</span>
<span className="chip">입문부터 차근차근</span>
</div>
</div>
@ -30,13 +30,34 @@ export default function LearnHubPage() {
</span>
</div>
<div className="grid-cards">
{group.items.map((course) => {
{group.items.map((course, index) => {
// external , /learn/<slug> .
const to = course.external || `/learn/${course.slug}`;
return (
<Link key={to} to={to} style={{ color: 'inherit' }}>
<div className="card card-clickable" style={{ height: '100%' }}>
<div style={{ fontSize: 26, marginBottom: 8 }}>{course.icon}</div>
{/* : index .
padStart(2,'0') 1 "01" 만들어 준다 (자릿수 맞추기). */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 8,
}}
>
<span style={{ fontSize: 26 }}>{course.icon}</span>
<span
style={{
fontSize: 13,
fontWeight: 800,
color: 'var(--primary)',
fontVariantNumeric: 'tabular-nums',
}}
>
{String(index + 1).padStart(2, '0')}
</span>
</div>
<div className="assign-title-row">
<span className="assign-title">{course.title}</span>
<span className="badge badge-primary">{course.level}</span>

View File

@ -0,0 +1,370 @@
// : "AI " ·
// AI ' ' 7 .
// (AI / )
// : API ' ' . global.css
// (step-card, code-block, tip, warn ) .
// · JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_TOOL_KINDS = `AI 코딩 도구, 크게 두 갈래
자동완성형 에디터 안에서 다음 코드를 '이어 써 주는' 도구
) GitHub Copilot, Cursor의 자동완성
느낌: 옆에서 타자를 지켜보다가 "이거 치려던 거지?" 하고
회색 글씨로 미리 보여주는 짝꿍
어시스턴트형 대화창에 질문하면 코드·설명으로 답하는 도구
) ChatGPT, Claude, Gemini
느낌: "이 에러 뭐야?", "이 함수 설명해 줘" 하고
말을 있는 24시간 대기 선배
공통점: '그럴듯한 답' 만드는 기계이지,
'항상 맞는 답' 보장하는 기계가 아니에요. (섹션 3에서 자세히!)`;
const CODE_BAD_GOOD_PROMPT = `같은 문제, 다른 질문 — 답의 품질이 하늘과 땅
나쁜 질문
"로그인이 안 돼요. 고쳐 주세요."
AI: "음... 코드를 보여주시면..." (추측만 잔뜩)
좋은 질문
[맥락] React 프론트에서 Spring Boot 백엔드로 로그인 요청을 보내는데,
[증상] 브라우저 콘솔에 CORS 에러가 뜨고 응답이 와요.
[코드] fetch('http://localhost:8080/api/login', {...}) 부분
[시도] 백엔드 컨트롤러에 @CrossOrigin은 붙여 봤어요.
[제약] Spring Boot 3 기준으로, 설정 파일로 해결하고 싶어요.
AI: 원인 후보를 좁혀서 바로 있는 답을 `;
const CODE_PROMPT_TEMPLATE = `좋은 질문 3요소 — 어디서 많이 본 구조죠?
1) 맥락 만들다가, 어떤 환경에서 (React? Spring Boot? Docker?)
2) 제약 어떤 조건으로 답해야 하는지 (버전, 라이브러리, 우리 규칙)
3) 예시 실제 코드·에러 메시지·기대한 결과 vs 실제 결과
이거, 30 룰에서 배운 "멘토에게 질문하는 법" 완전히 같아요!
"○○을 하려고 △△를 해봤는데 □□가 됩니다. 제 생각엔..."
사람에게 묻는 사람이 AI에게도 묻습니다.
질문 실력은 하나예요 상대가 사람이냐 AI냐만 다를 .`;
const CODE_VERIFY_CHECKLIST = `AI 답변 검증 4단계 — 복붙 전에 반드시!
1. 읽고 설명할 있는가?
"이 줄은 ~하는 코드"라고 말할 있어야 통과.
설명 하는 줄이 있으면 AI에게 "이 줄 뭐 하는 거야?" 되묻기.
2. 우리 프로젝트에 맞는가?
AI는 우리 코드베이스를 몰라요. 변수명·폴더 구조·버전이
우리 것과 맞는지 눈으로 대조.
3. 실제로 돌아가는가?
로컬에서 실행 정상 케이스 1 + 이상한 입력 1개는 테스트.
4. 근거를 교차 확인했는가?
낯선 API·설정이면 공식 문서를 열어보기.
AI는 존재하지 않는 함수도 아주 자신 있게 지어냅니다(할루시네이션).`;
const CODE_LEARNING_PROMPTS = `AI를 '선생님 모드'로 쓰는 프롬프트 모음
설명 요청 대신 이해를 달라고 하기
" 코드를 고쳐 주지 말고, 에러가 나는지
원리부터 차근차근 설명해 . 나는 고등학생 개발 수습이야."
"useEffect가 뭔지 택배 배송에 비유해서 설명해 줘."
퀴즈 요청 배운 시험 문제로 돌려받기
"방금 배운 React 상태 관리에 대해 5문제 퀴즈를 .
내가 답하면 채점하고, 틀린 것만 해설해 ."
" 함수에 버그를 하나 심어서 보여줘.
내가 찾아볼게. 찾으면 힌트를 하나씩 ."
핵심: "정답 줘" 아니라 "내가 풀게 도와줘"라고 시키면
AI는 세상에서 제일 참을성 있는 과외 선생님이 됩니다.`;
const CODE_SECURITY_RULES = `AWESOMEDEV AI 사용 보안 수칙 — 넣기 전에 3초만 생각!
절대 AI 입력창에 넣으면 되는
고객사 프로젝트의 소스 코드 (계약으로 금지된 경우가 많아요)
비밀번호, API , 토큰, .env 파일 내용
DB 접속 정보, 서버 IP + 계정 조합
고객·회원의 개인정보 (이름, 전화번호, 이메일...)
회사 내부 문서, 계약서, 단가표
넣어도 되는
교재용 학습 플랫폼(mirim-app)처럼 공개 가능한 학습 코드
문법 질문, 일반적인 에러 메시지 (경로· 값은 지우고!)
내가 연습용으로 직접 코드
애매하면? 넣지 말고 멘토에게 먼저 물어보기. 이것도 30 !`;
const CODE_GOOD_BAD_AT = `AI가 잘하는 것 vs 못하는 것 — 역할 분담표
잘하는 (믿고 맡기기) 못하는 (내가 챙기기)
반복적인 보일러플레이트 코드 우리 프로젝트 전체 구조 파악
문법 에러·오타 찾기 "이 기능이 정말 필요한가?" 판단
낯선 개념을 눈높이 설명 최신 버전·어제 나온 변경사항
코드에 주석·이름 다듬기 보안·개인정보 판단 (책임은 사람!)
"이런 방법도 있어" 선택지 제시 컨벤션·고객사와의 약속
테스트 케이스 아이디어 없는 함수를 지어내지 않기(가끔 지어냄)
비유하면: AI는 좋고 지식 많은 신입 알바생.
시키면 빨리 하는데, 가게 사정은 모르고 가끔 자신만만하게 틀려요.
일을 시키는 것도, 결과를 검수하는 것도, 책임지는 것도 '사장' .`;
const CODE_ERROR_PRACTICE = `실습: 이 에러, AI에게 어떻게 물어볼까?
상황 React 개발 서버를 켰더니 콘솔에:
TypeError: Cannot read properties of undefined (reading 'map')
at CourseList (CourseList.jsx:12)
1단계(나쁜 ): "에러 났어요 고쳐줘" 이러면 AI도 점쟁이가 됩니다.
2단계(직접 보기): 아래 빈칸을 채워 프롬프트를 완성하세요.
[맥락] 저는 React로 ______를 만들고 있어요.
[증상] 화면이 하얗게 나오고 콘솔에 에러가 떠요: (에러 전문 붙여넣기)
[코드] CourseList.jsx 12번째 근처 코드: (해당 부분 붙여넣기)
[시도] 저는 ______를 해봤는데 ______였어요.
[질문] undefined에서 map을 읽는다는 건지 원리도 설명해 주세요.
3단계(검증): AI 답을 받으면 섹션 3 체크리스트 4단계로 검증!
힌트: 에러의 단골 원인은 "데이터가 도착하기 전에 화면을 그려서"예요.
AI의 답이 방향인지 확인해 보세요.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: 'AI 코딩 도구의 시대' },
{ n: 2, label: '잘 시키는 법 = 좋은 질문' },
{ n: 3, label: '검증하는 습관' },
{ n: 4, label: '학습에 쓰는 법' },
{ n: 5, label: '회사 보안 규칙' },
{ n: 6, label: '잘하는 것·못하는 것' },
{ n: 7, label: '실습: 에러 질문하기' },
];
export default function AiToolsPage() {
return (
<div>
{/* 히어로: AI는 대신 커주는 지팡이가 아니라, 잘 부려야 하는 도구 */}
<div className="hero">
<div className="eyebrow">Course · 소프트웨어</div>
<h1>AI 도구와 함께 코딩하기<br /> 똑똑한 후배를 부리는 </h1>
<p>
요즘 개발자 옆에는 코드를 대신 주겠다는 AI가 대기 중이에요. 코스에서는
AI에게 <strong> 시키고, 답을 의심하고, 나의 공부로 바꾸는</strong> 가지 기술을
배웁니다 도구는 도구일 , 실력과 책임은 언제나 것이니까요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">실습 2</span>
<span className="chip">준비물: AI 하나 + 우리 플랫폼</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="AI 코딩 도구의 시대" sub="자동완성과 챗 어시스턴트, 두 명의 조수">
<p>
사이 개발 풍경이 바뀌었어요. 에디터가 다음 줄을 <strong>미리 주고</strong>,
모르는 에러는 검색 대신 <strong>AI에게 물어보는</strong> 일상이 됐죠.
여러분이 취업할 즈음엔 "AI 없이 코딩" "내비 없이 초행길 운전"만큼 드문 일이 거예요.
</p>
<Code>{CODE_TOOL_KINDS}</Code>
<p>
그런데 여기서 착각하면 되는 하나 있어요. AI는 <strong>운전대를 잡은 내비게이션</strong>
아니라 <strong>조수석의 내비게이션</strong>입니다. 길을 제안하지만, 핸들을 꺾고 사고의 책임을
지는 운전자 <strong></strong>예요. 코스 전체가 문장의 각론입니다.
</p>
<div className="tip">
<b>미리 보기</b> 코스의 실습은 개예요. 섹션 5에서 우리 플랫폼 코드로
"넣어도 되는 것" 판별 연습, 섹션 7에서 에러 메시지 프롬프트 작성 연습.
지금 쓰는 PC에서 바로 해볼 있습니다.
</div>
</Section>
<Section n={2} title="잘 시키는 법 = 좋은 질문" sub="맥락·제약·예시 — 30분 룰의 질문법 그대로">
<p>
AI에게 시키는 일의 품질은 <strong> 질문의 품질</strong> 절대 넘어요.
"알아서 잘 해줘"라고 하면 알아서 대충 옵니다. 처음 알바생에게 일을 시킨다고
생각해 보세요 가게 사정(맥락), 하면 되는 (제약), 완성 예시(예시)
말해 줘야 원하는 결과가 나오죠.
</p>
<Code>{CODE_BAD_GOOD_PROMPT}</Code>
<p>정리하면 좋은 질문의 3요소는 이렇습니다.</p>
<Code>{CODE_PROMPT_TEMPLATE}</Code>
<div className="tip">
<b>연습 요령</b> 프롬프트를 보내기 전에 스스로 물어보세요
<strong>"이 질문을 멘토님께 슬랙으로 보낸다면 부끄럽지 않은가?"</strong>
멘토에게 보내도 수준의 질문이면 AI도 좋은 답을 줍니다.
</div>
</Section>
<Section n={3} title="AI 답을 검증하는 습관" sub="이해 못한 코드는 내 코드가 아니다">
<p>
AI의 가장 무서운 점은 <strong>틀릴 때도 자신만만하다</strong> 거예요.
말투만 보면 정답 같은데, 존재하지 않는 함수를 지어내거나(이걸
<strong> 할루시네이션</strong>이라고 해요) 3 버전 기준으로 답하기도 합니다.
그래서 AI의 답은 '정답' 아니라 <strong>'검토 대기 중인 초안'</strong>으로 받아야 해요.
</p>
<div className="warn">
<b>그대로 복붙 금지!</b> 이해 못한 코드는 <strong> 코드가 아닙니다.</strong>
돌아가더라도 돌아가는지 모르면, 고장 났을 고장 났는지도 몰라요.
면접에서 "이 코드 왜 이렇게 짰어요?" "AI가 줬는데요..."라고 답할 없잖아요.
줄이라도 설명 하는 코드는 붙여넣기 전에 반드시 물어보고 이해하기.
</div>
<Code>{CODE_VERIFY_CHECKLIST}</Code>
<p>
귀찮아 보이지만, 4단계가 몸에 붙으면 오히려 <strong>속도가 빨라져요.</strong>
검증 없이 복붙한 코드가 터뜨리는 버그를 찾는 시간이, 처음부터 이해하고 쓰는
시간보다 걸리거든요. 소화 음식이 결국 배탈로 돌아오는 것과 같아요.
</p>
</Section>
<Section n={4} title="학습 도구로 쓰는 법" sub="답을 받는 기계가 아니라, 과외 선생님으로">
<p>
AI를 "숙제 대신 해 주는 친구" 쓰면 실력이 늘지 않아요. 하지만
<strong> "설명해 주는 선생님"</strong>으로 쓰면 이야기가 달라집니다.
부끄러운 질문을 번을 반복해도 짜증 내지 않고, 새벽 2시에도 대답해 주고,
수준에 맞춰 비유를 바꿔 주는 선생님은 세상에 AI뿐이에요.
</p>
<Code>{CODE_LEARNING_PROMPTS}</Code>
<p>
포인트는 <strong>주도권</strong>이에요. "답 줘" 주도권을 AI에게 넘기는 ,
"설명해 줘 / 퀴즈 내 줘 / 힌트만 줘" 주도권을 내가 쥐는 .
운동을 트레이너가 대신 주면 근육은 트레이너에게만 붙죠
코딩 근육도 똑같습니다.
</p>
<div className="tip">
<b>추천 루틴</b> 학습 센터에서 코스 하나를 끝낼 때마다 AI에게
"방금 배운 ○○에 대해 퀴즈 5문제 내 줘"라고 시켜 보세요.
배운 직후의 셀프 테스트가 기억을 가장 오래 남깁니다.
</div>
</Section>
<Section n={5} title="회사 규칙: AI에 넣으면 안 되는 것" sub="한 번 나간 데이터는 되돌릴 수 없다">
<div className="warn">
<b>가장 중요한 섹션입니다 보안 수칙</b><br />
AI 입력창에 넣은 내용은 <strong>회사 서버로 전송</strong>됩니다.
나간 데이터는 삭제 버튼이 없어요. 특히 <strong>고객사 프로젝트의 코드와
비밀 정보는 절대 AI에 넣지 않습니다.</strong> 고객사와의 계약에는 "코드를 외부에
제공하려면 사전 서면 승인이 필요하다" 조항이 들어 있는 경우가 많고, 이를 어기면
수습인 명의 실수가 <strong>회사 전체의 계약 위반</strong> 됩니다.
친구 비밀을 마음대로 SNS에 올리면 되는 것과 같아요 남의 것은 남의 .
</div>
<Code>{CODE_SECURITY_RULES}</Code>
<p>
"이 정도는 괜찮겠지?" 제일 위험한 순간이에요. 에러 메시지 하나를 붙여넣을 때도
안에 서버 경로, DB 이름, 토큰 값이 딸려 들어가지 않는지 <strong>지우고 나서</strong>
붙여넣는 습관을 들이세요.
</p>
<div className="tip">
<b>직접 확인해 보기 </b> 우리 Gitea(<span className="icode">edu.awesomedevapp.com:3000</span>)에서
여러분의 실습 저장소를 열고, 아무 파일이나 골라 스스로 판별해 보세요
"이 파일, AI에 넣어도 되는가?" 학습용 코드(), 그런데 만약 안에
<span className="icode">.env</span> 값이나 DB 비밀번호가 적혀 있다면()!
판단 근거를 소리 내어 말할 있으면 통과. 애매한 파일이 하나라도 있었다면
멘토에게 물어보는 것까지가 실습입니다.
</div>
</Section>
<Section n={6} title="AI가 잘하는 것, 못하는 것" sub="어디까지 맡기고 어디부터 내가 할까">
<p>
도구를 쓴다는 <strong>도구의 한계를 안다</strong> 뜻이에요.
망치는 박기엔 최고지만 나사를 조이라고 주면 망하죠.
AI도 잘하는 일과 못하는 일의 경계가 뚜렷합니다.
</p>
<Code>{CODE_GOOD_BAD_AT}</Code>
<p>
특히 오른쪽 열의 <strong>"판단" 들어가는 </strong> 전부 사람 몫이에요.
우리 스택(React·Spring Boot·PostgreSQL·Docker)에서 어떤 코드가 "돌아가는가"
AI가 도와줄 있지만, 코드가 우리 프로젝트에 "<strong>맞는가</strong>",
고객과의 약속을 지키는가는 코드 밖의 사정을 아는 사람만 판단할 있습니다.
</p>
<div className="tip">
<b>실무 감각</b> 선배 개발자들이 AI를 쓰는 비율을 보면, 코드를 '생성'시키는
못지않게 <strong>코드 리뷰·이름 짓기·설명 요청</strong> 많이 씁니다.
"이 함수 이름 더 좋은 거 없을까?", "이 코드의 문제점을 지적해 줘"
이런 질문은 위험 부담 없이 AI의 장점만 뽑아 쓰는 사용법이에요.
</div>
</Section>
<Section n={7} title="실습: 에러 메시지를 AI에게 잘 물어보기" sub="배운 것 총동원 — 프롬프트 직접 쓰기">
<p>
이제 실전이에요. 개발하다 만나는 가장 흔한 상황 <strong>빨간 에러 메시지</strong>
놓고, 섹션 2 3요소(맥락·제약·예시) 프롬프트를 직접 봅니다.
</p>
<Code>{CODE_ERROR_PRACTICE}</Code>
<div className="tip">
<b>직접 확인해 보기 </b> 지금 플랫폼에서 <span className="kbd">F12</span> 눌러
개발자 도구의 <span className="kbd">Console</span> 탭을 열어 두고 페이지를 돌아다니다가,
경고든 에러든 메시지가 하나라도 보이면 그걸 재료로 2단계 빈칸 프롬프트를
완성해 AI에게 보내 보세요. 없다면 위의 <span className="icode">TypeError</span> 예시
그대로 연습해도 좋아요. 받은 답은 <strong>반드시 섹션 3 체크리스트로 검증</strong>하고,
완성한 프롬프트와 AI의 답을 멘토에게 공유해서 피드백을 받으면 실습 완료!
</div>
<p>
프롬프트 하나는 재산이에요. 마음에 드는 질문 패턴이 생기면 메모장에
모아 두세요 선배들도 다들 자기만의 <strong>프롬프트 서랍</strong> 갖고 있답니다.
</p>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🤖 여기까지 왔다면</h3>
<p className="muted">
이제 여러분은 AI에게 <strong>맥락·제약·예시로 시키고</strong>, 답을
<strong> 4단계로 검증하고</strong>, 고객사의 것은 <strong>절대 넣지 않는</strong>
개발자예요. AI가 대신 코드도 결국 Git으로 관리되고 서버에 배포되니,{' '}
<Link to="/learn/git"><strong>Git과 협업</strong></Link> 코스로 코드를 안전하게
나누는 법을, <Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스로
코드가 달리는 길을 이어서 배워 보세요. 그리고 오늘 배운 프롬프트 3요소로
실습 과제 하나를 골라 AI와 함께(하지만 주도권은 내가!) 풀어 보는 , 그게 진짜 복습입니다.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,392 @@
// : " " " 500
// 60 ?" , ··
// . (8 )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_ROAD = `대역폭 = 도로의 "폭"(차선 수), 속도 = 차 한 대의 빠르기
1차선 도로 4차선 도로
🚗 🚗 🚗 🚗 🚗
초당 1 지나감 초당 4 지나감
(데이터) 최고 속도는 비슷해요.
차이는 "동시에 몇 대가 지나갈 수 있느냐" = 대역폭!
인터넷에서 말하는 "속도 500Mbps" 사실
차의 속도가 아니라 도로의 (초당 옮길 있는 )입니다.`;
const CODE_MBPS = `Mbps와 MB/s — 대문자 B 하나가 8배를 가릅니다
Mbps = Mega bit per second (비트: 0 아니면 1, 가장 작은 알갱이)
MB/s = Mega Byte per second (바이트 = 비트 8 묶음)
1 Byte = 8 bit 이므로:
100 Mbps ÷ 8 = 12.5 MB/s
500 Mbps ÷ 8 = 62.5 MB/s
1000 Mbps ÷ 8 = 125 MB/s (기가 인터넷)
통신사 광고는 숫자가 예쁜 Mbps로,
브라우저 다운로드 창은 MB/s로 표시해요.
"500메가 쓰는데 다운로드는 60밖에 안 나와!" 사실 정상입니다.`;
const CODE_DOWNLOAD_TIME = `4GB(=4,096MB) 게임 하나 받는 데 걸리는 시간 (이론상 최대치)
회선 MB/s 환산 걸리는 시간
100 Mbps 12.5 MB/s 5 28
500 Mbps 62.5 MB/s 1 6
1 Gbps 125 MB/s 33
계산법: 파일 크기(MB) ÷ (Mbps ÷ 8) =
실제로는 서버 사정·와이파이 손실 때문에 이보다 느려요. (섹션 5 참고)`;
const CODE_PING = `지연시간(ping) = 왕복 "반응 속도", 대역폭 = 한 번에 옮기는 "양"
수도관 비유:
대역폭 = 파이프의 굵기 (초당 리터?)
지연시간 = 수도꼭지를 순간 ~ 물이 나오기까지의 시간
굵은 파이프여도 물이 100m 밖에서 오면 물은 늦게 나와요.
기가 인터넷이어도 해외 서버면 ping은 높을 있습니다!
무엇이 중요할까?
온라인 게임 ping! (작은 데이터를 빠르게 주고받음)
영상 스트리밍 대역폭 ( 데이터를 계속 흘려받음)
파일 다운로드 대역폭
화상회의 (특히 ping이 높으면 대화가 어긋남)
ping 감각 기준: ~30ms 쾌적 / 50~100ms 게임에서 체감 / 200ms+ 답답`;
const CODE_PING_CMD = `# 터미널(cmd/PowerShell)에서 — 서버까지 왕복 시간 재기:
ping google.com
Ping google.com [142.250.x.x] 32바이트 데이터 사용:
142.250.x.x의 응답: 바이트=32 시간=34ms TTL=115
...
평균 = 34ms 이게 지연시간!
# 국내 서버와 비교해 보세요:
ping naver.com # 보통 5~20ms
ping edu.awesomedevapp.com # 우리 플랫폼 서버는 ms?
# 물리적 거리가 멀수록 ms가 커지는 관찰해 보세요.
# 빛도 태평양을 건너는 시간이 걸립니다!`;
const CODE_BOTTLENECK = `병목(bottleneck) — 전체 속도는 "가장 좁은 구간"이 정합니다
[ PC] [공유기] [통신사 회선] [상대 서버]
PC 공유기 구간
- 와이파이: ·거리·전자레인지 간섭으로 뚝뚝 떨어짐 (최대 병목 단골!)
- 오래된 랜선: Cat5(무印) 100Mbps가 한계 기가 회선이 무용지물
- 공유기 자체가 구형이면 여기서 막힘
통신사 회선
- 계약한 상품이 실제 상한 (500메가 상품이면 여기가 500)
상대 서버
- 서버가 붐비면 회선이 기가여도 느리게 내려줌
- 해외 서버는 국제 구간에서 느려지기도
기가 인터넷 + 구형 공유기 + 너머 와이파이
= 8차선 고속도로를 달려와서 골목길로 들어가는 셈이에요.`;
const CODE_SPEEDTEST = `속도 측정, 이렇게 해야 "진짜" 숫자가 나와요
측정 사이트: fast.com(넷플릭스), speedtest.net(Ookla),
benchbee.co.kr(국내 서버 기준)
체크리스트:
1) 다운로드·업로드· 가지를 모두 기록 (숫자 하나가 아님!)
2) 측정 서버 위치 확인 해외 서버로 재면 낮게 나오는 정상
3) 측정 유튜브·게임·다른 기기 다운로드 끄기 (도로를 나눠 쓰는 중이면
차선만 아니게 )
4) 시간대 바꿔서 2~3 측정 저녁 8~11시는 다들 쓰는 혼잡 시간
5) 유선과 무선을 따로 측정 (섹션 8 실습!)
읽는 :
다운로드 480Mbps / 업로드 490Mbps / 8ms 500메가 상품이면 정상
다운로드 90Mbps / 5ms 회선은 멀쩡, ①구간 의심!`;
const CODE_LAB = `실습: 유선 vs 무선 속도 비교 측정
준비물: 노트북(또는 PC), 랜선 1, 측정 사이트
순서
1) 무선(와이파이) 상태로 speedtest.net 측정
다운로드 / 업로드 / 3개를 표에 기록
2) 같은 자리에서 (와이파이는 편차가 커요)
3) 랜선을 공유기에 직접 연결, 와이파이 끄기
4) 같은 사이트에서 다시 2 측정 기록
5) (도전) 공유기에서 방으로 이동해 무선 재측정
기록표 예시:
구분 다운로드 업로드
무선 1 233 Mbps 180 Mbps 12ms
무선 2 198 Mbps 171 Mbps 15ms
유선 1 492 Mbps 488 Mbps 5ms
유선 2 489 Mbps 490 Mbps 5ms
무선 87 Mbps 60 Mbps 23ms
관찰 포인트:
- 유선이 얼마나 빠르고 "일정"한가? (편차도 성능이에요)
- 거리가 멀어지면 무선은 얼마나 떨어지나?
- 핑도 유선이 낮은 이유를 섹션 4 비유로 설명해 보기`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '대역폭 = 도로 폭' },
{ n: 2, label: 'Mbps vs MB/s' },
{ n: 3, label: '다운로드 시간 계산' },
{ n: 4, label: '지연시간(ping)' },
{ n: 5, label: '100메가 vs 기가' },
{ n: 6, label: '병목 찾기' },
{ n: 7, label: '속도 측정 제대로' },
{ n: 8, label: '실습: 유선 vs 무선' },
];
export default function BandwidthPage() {
return (
<div>
{/* 히어로: 이 코스가 풀어 줄 오해가 무엇인지 */}
<div className="hero">
<div className="eyebrow">Course · 네트워크</div>
<h1>네트워크 속도와 대역폭<br /> 500메가 인터넷인데 60밖에 나올까?</h1>
<p>
"인터넷 속도"라는 단어 안에는 사실 <strong>대역폭·지연시간·병목</strong>이라는
서로 다른 개념이 섞여 있어요. 코스에서 셋을 갈라서 이해하고, 마지막엔
<strong> 손으로 직접 측정</strong>해서 우리 인터넷의 진짜 실력을 확인합니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">측정 실습 2</span>
<span className="chip">준비물: PC + 랜선 1</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="대역폭은 도로의 '폭'이다" sub="빠른 차가 아니라 넓은 길">
<p>
"인터넷이 빠르다" 우리는 은근히 <strong>차가 빨리 달리는 모습</strong>
떠올려요. 그런데 랜선이나 광케이블 속에서 신호가 이동하는 속도 자체는 어느 회선이나
거의 비슷합니다(빛의 속도 근처!). 진짜 차이는 <strong>도로의 </strong>,
<strong> 동시에 얼마나 많은 데이터를 흘려보낼 있느냐</strong>예요.
이게 바로 <strong>대역폭</strong>(bandwidth)입니다.
</p>
<Code>{CODE_ROAD}</Code>
<p>
그래서 "기가 인터넷" "차가 기가급으로 빠른 인터넷" 아니라
<strong> "1초에 1기가비트를 옮길 수 있는 넓은 도로"</strong>라는 뜻이에요.
구분이 코스 전체의 뼈대입니다 섹션 4에서 "차가 출발해서 도착하기까지의
시간"(지연시간) 확실히 갈라 거예요.
</p>
<div className="tip">
<b>용어 정리</b> 일상에서 "인터넷 속도"라고 부르는 = 대부분 <strong>대역폭</strong>.
코스에서도 편의상 섞어 쓰지만, 머릿속에서는 항상 "도로 폭"으로 번역하세요.
</div>
</Section>
<Section n={2} title="Mbps와 MB/s — 8배의 비밀" sub="다운로드가 생각보다 느린 진짜 이유">
<p>
여기가 코스 최대의 <strong>"아하!" 포인트</strong>예요. 통신사가 말하는
<span className="icode"> 500Mbps</span> <strong>b</strong> <strong>bit</strong>(비트),
다운로드 창에 뜨는 <span className="icode">MB/s</span> <strong>B</strong>
<strong> Byte</strong>(바이트)입니다. 바이트 하나는 비트 8 묶음이라,
<strong> 같은 회선인데 숫자는 8분의 1</strong> 보여요.
</p>
<Code>{CODE_MBPS}</Code>
<p>
비유하면 통신사는 "1초에 계란 <strong>낱개</strong> 500개!"라고 광고하고,
브라우저는 "1초에 계란 <strong>한 판(8개들이)</strong> 62.5판"이라고 표시하는 거예요.
같은 양인데 단위가 달라서 작아 보이는 것뿐입니다.
</p>
<div className="warn">
<b>속지 마세요</b> 소문자 b와 대문자 B는 <strong>정확히 8</strong> 차이입니다.
"500메가 계약했는데 60밖에 안 나온다" 민원의 대부분은 고장이 아니라
<strong> 단위 착시</strong>예요. 나누기 8부터 해보고 이야기를 시작하세요.
</div>
<div className="tip">
<b>직접 확인해 보기</b> 브라우저에서 파일(: 리눅스 ISO나 게임 클라이언트)
내려받으며 다운로드 창의 <span className="icode">MB/s</span> 숫자에 <strong>×8</strong>
보세요. 우리 계약 상품(Mbps) 얼마나 가까운가요? 다운로드는
<span className="kbd">Ctrl</span>+<span className="kbd">J</span> 있어요.
(확인만 했으면 받을 필요 없이 취소해도 됩니다.)
</div>
</Section>
<Section n={3} title="다운로드 시간, 직접 계산해 보기" sub="파일 크기 ÷ (Mbps ÷ 8) = 초">
<p>
단위 변환을 배웠으니 바로 써먹어 봅시다. 4GB짜리 게임을 받는 얼마나 걸릴까요?
공식은 간단해요 <strong>파일 크기(MB) MB/s로 나누면 </strong> 나옵니다.
</p>
<Code>{CODE_DOWNLOAD_TIME}</Code>
<p>
표는 <strong>이론상 최대치</strong>예요. 실제로는 상대 서버가 그만큼 빨리
내려주거나(섹션 6), 와이파이에서 새거나 해서 걸립니다. 그래도 계산을
알면 "지금 다운로드가 <strong>정상 범위</strong>인지 이상하게 느린 건지"
감이 아니라 숫자로 판단할 있어요 개발자다운 습관이죠.
</p>
<div className="tip">
<b>암산 요령</b> Mbps를 8 나누기 어려우면 <strong>10으로 나눠서 조금 후하게</strong>
잡으세요. 500Mbps 50MB/s쯤. 어림값으로도 "이상 감지"에는 충분합니다.
</div>
</Section>
<Section n={4} title="지연시간(ping) — 대역폭과는 다른 축" sub="게임은 ping, 다운로드는 대역폭">
<p>
도로가 아무리 넓어도(대역폭), 목적지가 멀면 차가 <strong>도착하기까지의
시간</strong> 오래 걸려요. 왕복 시간이 <strong>지연시간</strong>(latency)이고,
그걸 재는 도구 이름을 따서 흔히 <strong>ping</strong>()이라고 부릅니다.
단위는 <strong>ms</strong>(밀리초, 1000분의 1).
</p>
<Code>{CODE_PING}</Code>
<p>
그래서 <strong>"기가 인터넷인데 게임이 랙 걸려요"</strong> 성립해요. 게임은
"내 캐릭터 움직였어!" 같은 <strong>작은 메시지를 아주 자주</strong> 주고받는데,
이건 도로 폭이 아니라 <strong>왕복 시간</strong> 싸움이거든요. 반대로 영화
다운로드는 핑이 200ms여도 일단 흐르기 시작하면 대역폭만큼 콸콸 받습니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 터미널에서 국내 서버와 해외 서버의 핑을 재서 비교해 보세요.
숫자 차이가 <strong>물리적 거리</strong>입니다.
</div>
<Code>{CODE_PING_CMD}</Code>
</Section>
<Section n={5} title="100메가 vs 기가 — 체감은 언제 갈릴까" sub="10배 비싸도 10배 빠르게 '느껴지지는' 않는 이유">
<p>
기가 인터넷은 100Mbps의 10배지만, <strong>웹서핑·유튜브에서는 체감이 거의 없어요.</strong>
유튜브 풀HD는 5~8Mbps, 4K도 20~25Mbps면 충분해서 100메가 도로도 텅텅 남거든요.
카톡·검색·우리 학습 플랫폼 이용도 마찬가지 도로 폭보다 <strong>핑과 서버 응답</strong>
체감을 좌우합니다.
</p>
<p>
차이가 벌어지는 순간은 <strong> 데이터를 한꺼번에</strong> 옮길 때예요.
섹션 3 표를 다시 보세요 4GB 게임이 <strong>5 33</strong>.
수십 GB짜리 게임 업데이트, 4K 영상 원본 업로드, 그리고 개발자에겐
<span className="icode"> docker pull</span> GB 이미지를 받을 때가 그렇죠.
하나, <strong>가족 여럿이 동시에</strong> 4차선(100M) 명만 4K를 봐도
붐비지만, 40차선(1G) 여유롭습니다.
</p>
<div className="tip">
<b>정리</b> "혼자서 웹서핑·영상 시청 위주" 100메가도 충분,
"대용량 다운로드가 잦거나 여러 명이 동시에" 쓰면 기가가 값을 해요.
속도 상품 선택도 결국 <strong> 사용 패턴 분석</strong> 문제입니다.
</div>
</Section>
<Section n={6} title="병목은 어디서 생길까" sub="전체 속도는 가장 좁은 구간이 정한다">
<p>
8차선 고속도로를 신나게 달려도 마지막에 <strong>1차선 골목</strong> 지나야 한다면,
전체 여행 속도는 골목이 정해요. 네트워크도 똑같습니다 PC에서 서버까지의 길에서
<strong> 가장 좁은 구간</strong> 체감 속도가 됩니다. 좁은 구간을
<strong> 병목</strong>(bottleneck, 병의 좁아지는 부분)이라고 해요.
</p>
<Code>{CODE_BOTTLENECK}</Code>
<p>
가정에서 1순위 용의자는 거의 항상 <strong>와이파이</strong>예요. 전파는 벽을 지날 때마다
약해지고, 옆집 공유기·전자레인지와 채널을 나눠 씁니다. 2순위는 <strong>오래된 케이블과
구형 공유기</strong> 지난 코스에서 배운 랜선 카테고리(Cat5e부터 기가 지원!) 여기서
다시 등장하죠. 그리고 쪽이 멀쩡해도 <strong>상대 서버</strong> 붐비면 느립니다.
우리 회사 서버도 AWS EC2 인스턴스 사양과 회선에 따라 내려줄 있는 양에 한계가 있어요
서버 대역폭도 누군가 설계한 자원이라는 , 백엔드를 배울 다시 만나게 됩니다.
</p>
<div className="warn">
<b>흔한 착각</b> "인터넷이 느려요 → 통신사에 전화"부터 하기 쉽지만, 병목이
(와이파이·케이블·공유기) 있으면 통신사는 해줄 없어요.
<strong> 어느 구간이 좁은지 먼저 측정으로 갈라내는 </strong> 그게 다음 섹션입니다.
</div>
</Section>
<Section n={7} title="속도 측정 사이트, 제대로 쓰기" sub="숫자 하나 찍고 끝이 아니에요">
<p>
측정 사이트는 <strong> PC와 측정 서버 사이</strong> 데이터를 왕창 흘려보내서 도로
폭을 재는 도구예요. 그런데 조건을 갖추고 재면 <strong>병목 숫자</strong> 나와서
엉뚱한 결론을 내리게 됩니다. 측정도 실험이에요 조건 통제가 생명!
</p>
<Code>{CODE_SPEEDTEST}</Code>
<p>
특히 마지막 줄을 보세요. <strong>다운로드는 낮은데 핑은 좋은</strong> 패턴이 나오면
회선() 아니라 구간() 병목일 가능성이 커요. 이렇게 다운로드·업로드·
<strong> 숫자를 조합해서 범인을 추리</strong>하는 측정의 진짜 목적입니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 바로 <span className="icode">fast.com</span> 들어가
측정하고, 이어서 <span className="icode">speedtest.net</span>에서도 보세요.
사이트 숫자가 다르죠? 측정 서버 위치와 방식이 달라서예요
그래서 속도는 <strong> 번의 숫자가 아니라 여러 번의 경향</strong>으로 읽습니다.
</div>
</Section>
<Section n={8} title="실습 — 유선 vs 무선 비교 측정" sub="이 코스의 졸업 과제">
<p>
배운 전부 동원하는 실습입니다. 같은 회선을 <strong>랜선(유선) 와이파이(무선)</strong>
번갈아 재서, 우리 (또는 실습실) 병목이 어디인지 데이터로 밝혀내 보세요.
</p>
<Code>{CODE_LAB}</Code>
<p>
측정이 끝나면 결과표를 가지고 <strong> 문장으로 결론</strong> 보세요.
"우리 회선의 실제 대역폭은 ___Mbps다. 무선은 유선 대비 ___% 나왔고,
병목은 ___ 구간으로 추정된다." 숫자로 말하는 연습, 이게 엔지니어의 보고서예요.
</p>
<div className="tip">
<b> 해보기</b> 측정한 유선 대역폭으로 섹션 3 공식을 써서 "4GB 파일 다운로드
예상 시간" 계산한 , 실제로 파일을 받아 예측과 비교해 보세요.
예측 측정 오차 분석, 실험의 3단계를 그대로 해보는 겁니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🚗 여기까지 왔다면</h3>
<p className="muted">
이제 "인터넷이 느려요" <strong>대역폭 문제인지, 지연시간 문제인지, 병목이
어느 구간인지</strong> 쪼개서 말할 있게 됐어요. 8 나누는 습관과 측정
체크리스트는 평생 갑니다. 데이터가 달리는 자체가 궁금해졌다면{' '}
<Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스에서
랜선부터 해저 케이블까지 이어서 배우고, 측정 결과표는 멘토에게 공유해
함께 병목 추리를 보세요.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,404 @@
// : " NoSQL "
// ( Redis) ,
// NoSQL(MongoDB) 8 .
// ( / Redis NoSQL vs RDB MongoDB 304 )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_CACHE_LAYERS = `캐시 = "자주 쓰는 물건은 책상 위에" 전략
데이터를 가지러 가는 거리 (가까울수록 빠름):
[브라우저 캐시] 책상 서랍 0.001, 네트워크도
[서버 캐시] 교무실 사물함 0.01, 메모리에서 바로
[DB(PostgreSQL)] 도서관 서고 0.1~, 디스크를 뒤져서 찾음
같은 질문("인기 코스 목록 줘") 1초에 100 온다면?
100 서고까지 다녀올 필요 없죠.
가져와서 사물함에 넣어 두고 꺼내 주면 됩니다.`;
const CODE_HIT_MISS = `캐시 히트(Hit) vs 캐시 미스(Miss)
요청: "인기 코스 목록 주세요"
캐시 확인 있다! > 바로 응답 = 캐시 히트 🎯
없다... > DB에 다녀옴 > 응답하면서
캐시에도 복사해 = 캐시 미스
미스가 나야 그다음부터 히트가 나요.
"첫 손님만 오래 기다리고, 뒷손님은 전부 즉시" 구조.
히트율(hit rate) = 히트 횟수 ÷ 전체 요청
히트율 90% DB 부담이 1/10 줄어든 셈이에요.`;
const CODE_REDIS_KV = `Redis = 메모리에 사는 초고속 키-값(Key-Value) 저장소
포스트잇 벽이라고 생각하세요:
(제목) (내용)
"course:popular" "[네트워크, 코딩기초, ...]"
"user:42:name" "김미림"
"visit:today" "1024"
명령도 그만큼 단순해요:
SET user:42:name "김미림" 붙이기
GET user:42:name 떼서 읽기 "김미림"
EXPIRE user:42:name 60 60 자동 폐기(유통기한!)
INCR visit:today 숫자 1 올리기 (방문자 카운터)
빠른가? 가지 이유:
1) 메모리(RAM) 디스크보다 수백~수천 빠른 동네
2) 해시로 찾음 키를 계산 번으로 위치를 바로 알아냄.
책을 처음부터 뒤지는 아니라 "그 포스트잇은 항상 저 자리" .`;
const CODE_INVALIDATION = `캐시 무효화(Invalidation) — 컴퓨터 과학의 2대 난제 중 하나
문제 상황:
10:00 캐시에 저장: "인기 1위 = 네트워크의 이해"
10:05 실제 DB 변경: 인기 1위가 "코딩 기초" 바뀜
10:06 사용자 접속 캐시가 답함: "1위는 네트워크의 이해" 낡은 정보!
우유 유통기한 비유:
캐시는 냉장고 우유예요. 문제는
"언제까지 신선한가?" 미리 없다는 .
유통기한 짧게(10) 신선하지만 DB에 자주 다녀옴 (캐시 효과 )
유통기한 길게(1시간) 빠르지만 낡은 정보를 보여줄 위험
대표 전략 가지:
TTL(Time To Live) "60초 지나면 무조건 버려" 단순, 최대 60 낡음 감수
즉시 삭제 DB를 바꾸는 순간 캐시도 지움 정확, 지울 곳을
하나라도 빼먹으면 유령 데이터가 남음(이게 어려움!)`;
const CODE_RDB_VS_NOSQL = `RDB(관계형 DB) vs NoSQL — 표 vs 자유 문서
RDB (PostgreSQL) = 엑셀
id name email (컬럼) 미리 고정됨
1 김미림 a@... 모든 행이 같은 모양
2 이수습 b@...
· 표끼리 관계(JOIN) 맺기가 강점
· 스키마(모양) 바꾸려면 전체를 손봐야
NoSQL (MongoDB) = 자유 양식 문서 뭉치
{ "name": "김미림", "email": "a@...",
"badges": ["네트워크 수료", "Git 입문"] }
{ "name": "이수습", "phone": "010-...",
"mentor": { "name": "박선배" } } 문서마다 모양이 달라도 OK!
· 중첩·배열이 자연스러움 (JSON 그대로)
· 대신 문서끼리 엮는(JOIN 같은) 일은 약함`;
const CODE_MONGO_TASTE = `MongoDB 한 입 — SQL과 나란히 놓고 보기
같은 SQL (PostgreSQL) MongoDB
저장 INSERT INTO users (name) db.users.insertOne(
VALUES ('김미림'); { name: "김미림" })
조회 SELECT * FROM users db.users.find(
WHERE name = '김미림'; { name: "김미림" })
용어도 짝이 맞아요:
테이블(table) 컬렉션(collection)
(row) 문서(document)
(column) 필드(field)
MongoDB의 문서는 사실상 JSON 프론트엔드에서 다루는
모양 그대로 저장하고 그대로 꺼냅니다.`;
const CODE_DECISION = `언제 뭘 쓰나 — 결정 기준 치트시트
질문 1. 데이터가 사라지면 큰일 나나?
YES PostgreSQL (수강 기록, 계정, 성적 원본은 RDB에)
NO Redis 후보 (다시 계산하면 되는 : 인기 목록, 세션, 카운터)
질문 2. 모양(스키마) 자주 바뀌거나 문서마다 다른가?
YES MongoDB 후보 (설문 응답, 활동 로그, 형태가 들쑥날쑥한 )
NO PostgreSQL (모양이 고정이면 표가 최고)
질문 3. 관계·집계가 복잡한가? (JOIN, 트랜잭션)
YES PostgreSQL (은행 이체처럼 "전부 성공 or 전부 취소" 필요할 )
우리 학습 플랫폼이라면:
회원·코스·수강기록 PostgreSQL (원본, 관계 중요)
로그인 세션·조회수 Redis (빠르고, 날아가도 재생성 가능)
자유 양식 학습일지 MongoDB (학생마다 형식이 다르니까)
핵심: 셋은 경쟁자가 아니라 역할 분담. 원본은 RDB,
속도는 캐시, 자유 형식은 문서 DB 같이 씁니다.`;
const CODE_304_LAB = `실습: 브라우저 캐시를 눈으로 확인하기 (상태 코드 304)
우리 플랫폼(edu.awesomedevapp.com) 열고 F12 Network
"Disable cache" 체크박스가 꺼져 있는지 확인
F5 새로고침 요청 목록에서 Status 열을 보세요
Status 200 서버가 파일 전체를 보내줌 ( 방문)
Status 304 "Not Modified — 안 바뀌었어, 네가 가진 거 써!"
본문 없이 답장만 . 이게 브라우저 캐시 히트!
(memory cache) 아예 서버에 묻지도 않고 메모리에서 꺼냄 최강 히트
(disk cache) 디스크에 저장해 사본 사용
304 응답을 클릭 Headers에서 찾아보세요:
If-Modified-Since / ETag 브라우저: "내 사본은 이 버전인데?"
서버가 비교해 보고 같으면 304, 다르면 200 + 파일
이번엔 Ctrl+Shift+R (강력 새로고침) 전부 200으로 바뀌죠?
"캐시 무시하고 전부 새로 줘"라고 강제한 거예요.
섹션 4 무효화를 사용자가 수동으로 !`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '캐시란?' },
{ n: 2, label: '히트와 미스' },
{ n: 3, label: 'Redis' },
{ n: 4, label: '캐시 무효화' },
{ n: 5, label: 'NoSQL vs RDB' },
{ n: 6, label: 'MongoDB 한 입' },
{ n: 7, label: '선택 기준' },
{ n: 8, label: '실습: 304' },
];
export default function CacheNosqlPage() {
return (
<div>
{/* 히어로: 캐시와 NoSQL, 두 가지 맛보기를 한 코스에 */}
<div className="hero">
<div className="eyebrow">Course · 서버와 데이터</div>
<h1>캐시와 NoSQL 맛보기</h1>
<p>
자주 쓰는 물건을 책상 위에 두듯, 자주 쓰는 데이터를 가까이 두는 <strong>캐시</strong>예요.
그리고 모든 데이터가 엑셀 표처럼 반듯하진 않다는 인정하는 <strong>NoSQL</strong>이고요
코스에서 입씩 맛봅니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">직접 확인 실습 3</span>
<span className="chip">선수 지식: 데이터베이스 기초</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="캐시 — 자주 쓰는 물건은 책상 위에" sub="브라우저 캐시부터 서버 캐시까지, 원리는 하나">
<p>
시험공부할 자주 보는 교과서는 <strong>책상 </strong> 두고, 가끔 보는 책은 책장에,
거의 보는 책은 도서관에 두죠. 매번 도서관까지 걸어가면 공부가 되니까요.
컴퓨터도 똑같은 전략을 써요 이게 <strong>캐시(cache)</strong>입니다.
</p>
<Code>{CODE_CACHE_LAYERS}</Code>
<p>
여러분은 이미 캐시의 수혜자예요. 어떤 사이트가 <strong> 번째 방문부터 훨씬 빨리</strong> 열리는
이유 브라우저가 이미지·CSS·JS 파일을 PC에 <strong>브라우저 캐시</strong> 저장해 뒀다가
다시 쓰기 때문이에요. 서버 쪽에서도 마찬가지로, DB까지 가지 않고 메모리에서 바로 답하는
<strong> 서버 캐시</strong> 둡니다. 위치만 다를 <strong>"멀리 안 가고 가까운 사본을 쓴다"</strong>
원리는 하나예요.
</p>
<div className="tip">
<b>미리 보기</b> "내 브라우저가 정말 캐시를 쓰고 있나?" 섹션 8에서
개발자 도구로 직접 눈으로 확인할 거예요. 그때까지 페이지를 닫지 마세요!
</div>
</Section>
<Section n={2} title="캐시 히트와 캐시 미스" sub="사물함에 있으면 히트, 없으면 미스">
<p>
캐시에 물어봤을 <strong>찾는 있으면 히트(hit)</strong>, <strong>없으면 미스(miss)</strong>예요.
사물함을 열었는데 교과서가 있으면 히트 바로 수업 들어가면 되고,
없으면 미스 도서관(DB)까지 다녀와야 하죠. 다녀오는 김에 사물함에 넣어 두면,
다음 사람부터는 히트입니다.
</p>
<Code>{CODE_HIT_MISS}</Code>
<p>
그래서 캐시의 성적표는 <strong>히트율</strong>이에요. 히트율이 높을수록 DB는 한가해지고
응답은 빨라집니다. 반대로 히트율이 낮으면? 사물함 확인하는 수고만 늘고 결국 매번
도서관에 가는 셈이라, <strong>캐시를 두는 오히려 손해</strong> 수도 있어요.
"모든 걸 캐시하면 빨라진다" 아니라 <strong>"자주 또 찾는 것"</strong> 캐시해야 하는 이유죠.
</p>
<div className="warn">
<b>흔한 오해</b> 캐시는 원본이 아니라 <strong>사본</strong>이에요. 캐시가 통째로 날아가도
서비스는 (느려질 ) 죽지 않아야 정상입니다. 원본은 언제나 DB에 있어야 해요.
</div>
</Section>
<Section n={3} title="Redis — 메모리에 사는 키-값 창고" sub="포스트잇 벽처럼 단순해서 빠르다">
<p>
서버 캐시의 대표 선수가 <strong>Redis</strong>예요. 구조는 놀랄 만큼 단순합니다
<strong> (이름표)</strong> 주면 <strong>(내용)</strong> 돌려주는 전부예요.
벽에 붙은 포스트잇처럼 "제목 보고 → 떼서 읽기", .
</p>
<Code>{CODE_REDIS_KV}</Code>
<p>
빠른 비결은 가지예요. 첫째, 데이터를 디스크가 아니라 <strong>메모리(RAM)</strong> 둡니다.
책장(디스크) 아니라 <strong>손에 들고 있는</strong> 셈이라 꺼내는 속도의 차원이 달라요.
둘째, 키를 <strong>해시</strong>라는 계산으로 찾아요. "김미림 자료가 어디 있지?" 하고
뒤지는 아니라, 이름을 계산기에 넣으면 <strong>"3번 칸"</strong>이라고 위치가 바로 나오는
방식이라 데이터가 백만 개여도 찾는 시간이 거의 같습니다.
</p>
<p>
대신 메모리는 비싸고, <strong>서버가 꺼지면 내용이 날아갈 있어요</strong>.
그래서 Redis에는 "날아가도 다시 만들 수 있는 것"(캐시, 세션, 실시간 카운터) 담고,
날아가면 되는 원본은 PostgreSQL에 두는 정석입니다 섹션 2 경고 그대로죠.
</p>
</Section>
<Section n={4} title="캐시 무효화는 왜 어려울까" sub="유통기한을 미리 알 수 없는 우유">
<p>
여기까지 들으면 캐시는 만능 같지만, 진짜 어려운 문제가 하나 남아 있어요.
<strong> 원본이 바뀌었는데 캐시가 옛날 사본을 계속 주면?</strong> 사용자는 낡은 정보를
보게 됩니다. 낡은 사본을 골라 버리는 일이 <strong>캐시 무효화(invalidation)</strong>인데,
"컴퓨터 과학에서 어려운 문제 둘 중 하나"라는 농담이 있을 정도예요.
</p>
<Code>{CODE_INVALIDATION}</Code>
<p>
어려운 이유의 본질은 <strong>미래를 모른다</strong> 거예요. 우유 유통기한은 병에 적혀
있지만, "인기 코스 1위" 언제 바뀔지는 아무도 몰라요. 그래서 실무는 완벽한 대신
<strong> 타협</strong> 골라요 "최대 60초 낡은 건 봐준다"(TTL)거나, "바꾸는 쪽이
책임지고 캐시도 지운다"(즉시 삭제)거나. 어느 쪽이든 <strong>얼마나 낡아도 되는 데이터인가</strong>
먼저 정해야 캐시 설계가 시작됩니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 우리 플랫폼 어딘가의 프로필 사진이나 공지를 바꿔 있나요?
바꿨는데 화면에 예전 것이 보이면 방금 배운 <strong>무효화 문제를 실제로 목격</strong> 거예요.
그때 <span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd">R</span>
(강력 새로고침) 누르면 해결되죠? 브라우저 캐시를 수동으로 무효화한 겁니다.
다음에 상황을 만나면 "아, 캐시가 낡았구나"라고 진단해 보세요.
</div>
</Section>
<Section n={5} title="NoSQL vs RDB — 표 vs 자유 문서" sub="모든 데이터가 엑셀에 들어맞진 않는다">
<p>
우리가 쓰는 <strong>PostgreSQL</strong> <strong>관계형 DB(RDB)</strong>예요.
데이터를 <strong></strong> 넣죠 열이 미리 정해져 있고, 모든 행이 같은 모양이어야 해요.
반듯하고 검증하기 좋지만, 세상 모든 데이터가 표에 들어맞진 않아요.
학생마다 형식이 제각각인 <strong>자유 양식 학습일지</strong> 표에 구겨 넣으려면
빈칸투성이 열이 수십 생겨 버립니다.
</p>
<Code>{CODE_RDB_VS_NOSQL}</Code>
<p>
<strong>NoSQL</strong> "Not Only SQL" 말고 다른 모양도 쓰자는 흐름이에요.
방금 배운 Redis(-) NoSQL의 갈래고, 이번 섹션의 주인공인
<strong> 문서(document) 방식</strong> 그중 하나예요. 문서 방식은 데이터를
<strong> JSON 문서</strong> 그대로 저장합니다. 규격 원고지(RDB)
자유 양식 리포트(NoSQL) 차이 어느 쪽이 좋은 아니라 <strong>글의 종류가 다른</strong> 거예요.
</p>
</Section>
<Section n={6} title="MongoDB 한 입" sub="JSON을 그대로 넣고 그대로 꺼내는 DB">
<p>
문서형 NoSQL의 대표가 <strong>MongoDB</strong>예요. React에서 다루는
객체(<span className="icode">{'{ name: "김미림" }'}</span>) 모양 그대로 저장하고,
모양 그대로 꺼냅니다. 표로 바꿨다가 다시 객체로 조립하는 번역 과정이 없어서,
프론트엔드 개발자에게 유난히 친숙하게 느껴지는 DB죠.
</p>
<Code>{CODE_MONGO_TASTE}</Code>
<p>
유연함의 대가도 있어요. 표처럼 <strong>모양을 강제하지 않으니</strong>,
어떤 문서엔 <span className="icode">email</span> 있고 어떤 문서엔 없는 상황을
코드가 알아서 감당해야 해요. RDB라면 DB가 "그 열은 필수야!"라고 막아 줬을 실수를,
NoSQL에선 개발자가 스스로 지켜야 합니다. <strong>자유엔 책임이 따른다</strong> DB 세계도 똑같아요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> MongoDB가 없어도 문서 감각은 지금 바로 익힐 있어요.
우리 플랫폼에서 <span className="kbd">F12</span> <span className="kbd">Network</span>
새로고침 <span className="icode">api</span> 시작하는 요청을 클릭
<span className="kbd">Response</span> 보세요. 서버가 보낸 <strong>JSON</strong> 보이죠?
MongoDB는 바로 모양을 통째로 저장하는 DB예요. 중괄호 안에 배열, 배열 안에 객체
표로는 담기 힘든 <strong>중첩 구조</strong> 직접 찾아보세요.
</div>
</Section>
<Section n={7} title="언제 뭘 쓰나 — 결정 기준" sub="경쟁이 아니라 역할 분담">
<p>
"그래서 PostgreSQL, Redis, MongoDB 중에 뭐가 제일 좋아요?"
"숟가락, 포크, 젓가락 중에 뭐가 제일 좋아요?" 같은 질문이에요.
<strong> 음식(데이터) 성격</strong> 따라 고르는 겁니다. 실무에서 실제로 던지는
질문 개로 정리해 볼게요.
</p>
<Code>{CODE_DECISION}</Code>
<p>
실제 서비스는 대부분 <strong>여럿을 같이</strong> 씁니다. 원본과 관계는 RDB가 지키고,
앞에 Redis가 속도를 붙이고, 형태가 자유로운 데이터는 문서 DB가 받는 식이죠.
우리 회사 AWESOMEDEV의 서비스들도 PostgreSQL을 원본 창고로 쓰면서 필요한 곳에
캐시를 얹는 구조예요. 기능을 설계할 <strong>" 데이터는 사라지면 되나?
모양이 고정인가? 얼마나 자주 읽나?"</strong> 질문부터 던지는 습관을 들여 보세요.
</p>
</Section>
<Section n={8} title="실습 — 브라우저 캐시를 눈으로 확인하기" sub="상태 코드 304, 캐시 히트의 영수증">
<p>
약속대로, 섹션 1에서 배운 브라우저 캐시가 <strong>진짜로 일하고 있다는 증거</strong>
찾으러 갑니다. 열쇠는 상태 코드 <strong>304 Not Modified</strong> 서버가
"그 파일 안 바뀌었어, 네가 갖고 있는 사본 그대로 써!"라고 답하는 신호예요.
파일 본문 없이 답장만 오니까 <strong>전송량이 거의 0</strong>입니다.
</p>
<Code>{CODE_304_LAB}</Code>
<p>
④의 <span className="icode">ETag</span> 재밌는 부분이에요. 브라우저가
"내 사본의 버전 번호는 이거야"라고 함께 보내면, 서버는 파일을 다시 보내는 대신
<strong> 버전만 비교</strong>해요. 같으면 304(그대로 ), 다르면 200( 걸로 받아).
섹션 4에서 배운 무효화 문제를 <strong>"버전 확인"</strong>이라는 방식으로 푸는 현장을
지금 눈으로 겁니다.
</p>
<div className="tip">
<b>직접 확인해 보기 (심화)</b> Network 상단의 <strong>Size </strong> 정렬해 보세요.
<span className="icode">(memory cache)</span>·<span className="icode">(disk cache)</span> 표시된
요청과 실제 KB가 찍힌 요청의 <strong>Time</strong> 비교하면, 캐시 히트가 얼마나 빠른지
숫자로 보입니다. 스크린샷을 찍어 멘토에게 "여기가 히트, 여기가 미스"라고 설명해 보세요
설명이 되면 코스는 통과예요.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🗄 여기까지 왔다면</h3>
<p className="muted">
이제 여러분은 <strong>캐시</strong>(가까운 사본으로 속도 벌기, 히트/미스, 무효화의 어려움){' '}
<strong>NoSQL</strong>( 대신 문서, Redis와 MongoDB, 역할 분담) 입씩 맛봤어요.
핵심은 하나 <strong>데이터의 성격에 맞는 창고를 고른다</strong> .
표의 세계를 단단히 다지고 싶다면 <Link to="/learn/database"><strong>데이터베이스 기초</strong></Link> 코스로,
데이터들이 어떤 길로 오가는지 궁금하다면 <Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스로
이어 가세요. 그리고 과제 우리 플랫폼에서 <strong>"캐시하면 좋을 데이터 후보 3개"</strong> 골라
섹션 7 질문으로 검증한 멘토와 이야기해 보세요.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,367 @@
// : "CI/CD "
// , CI( ) CD( )
// 7 .
// ( CI CD : )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_MANUAL_DEPLOY = `우리 플랫폼의 실제 배포 과정 (솔직 공개 — 전부 사람 손입니다)
1) PC에서 프론트 빌드 npm run build
2) 백엔드 빌드 ./mvnw package .jar 파일 생성
3) 빌드 결과물을 서버로 복사 scp EC2에 업로드
4) 서버에 SSH 접속 ssh ubuntu@서버주소
5) 돌고 있던 버전 중지
6) 버전 실행 + Caddy 확인
7) 브라우저 열고 "잘 뜨나...?" 새로고침 연타
되면 15. 단계라도 실수하면? 원인 찾느라 1시간.
그리고 5)~6) 사이엔 사이트가 잠깐 죽어 있어요.`;
const CODE_HUMAN_ERROR = `사람 손 배포에서 실제로 일어나는 사고 유형
사고 원인
"어? 로컬에선 됐는데요" 빌드 하고 파일을 scp함
서버에서 500 에러 폭발 테스트 없이 배포 버그 동승
새벽 배포 아침에 장애 졸린 사람이 6단계 5단계만
"누가 언제 뭘 배포했지?" 기록이 사람 기억 속에만 있음
공통점: 도구가 아니라 '사람의 컨디션' 품질이 달려 있음`;
const CODE_CI_FLOW = `CI (Continuous Integration, 지속적 통합)
= 코드를 합칠 때마다 로봇이 자동으로 검사
[내가 git push] > [CI 로봇이 깨어남]
빌드가 되나? npm run build / mvnw package
테스트 통과? 자동 테스트 실행
결과 통보 초록불 / 빨간불 + 어디서 깨졌는지
핵심: 검사 기준이 '사람 기분' 아니라 '기계의 체크리스트'.
매번, 모든 커밋에, 똑같은 잣대로.`;
const CODE_CD_FLOW = `CD (Continuous Deployment, 지속적 배포)
= CI 검사를 통과하면 서버 배포까지 자동으로
수동 시절: push (사람이 빌드) (사람이 scp) (사람이 재시작)
CD 이후: push CI > 로봇이 알아서 서버에 버전 반영
사람이 하는 : git push 하나.
로봇이 하는 : 빌드, 검사, 업로드, 교체, 확인 전부.
비유: 수동 배포 = 손빨래 ( 받고, 비비고, 헹구고, 널고...)
CD = 세탁기 버튼 (넣고 버튼 . 매번 같은 품질)`;
const CODE_PIPELINE = `파이프라인 = 컨베이어 벨트. 한 정거장이라도 실패하면 그 자리에서 멈춥니다.
[git push]
[ 빌드] React·Spring Boot 컴파일 "일단 조립은 되니?"
통과
[ 테스트] 자동 테스트 실행 "조립품이 제대로 작동하니?"
통과
[ 배포] Docker 이미지 EC2 서버에 버전 교체
완료
[ 확인] "사이트 살아 있니?" 헬스체크
어디서든 실패하면 배포 + 개발자에게 알림
불량품이 손님(사용자)에게 도착하기 전에 벨트가 멈추는 !`;
const CODE_TOOLS = `파이프라인을 굴려 주는 도구들 (개념만 알아 두면 충분!)
도구 특징
Gitea Actions 우리 Gitea에 내장. 저장소에 워크플로 파일을
넣으면 push 때마다 실행. 가장 우리랑 가까움
Jenkins 역사 깊은 자동화 서버. 대기업에 아직 많음.
뭐든 시킬 있지만 직접 설치·관리 필요
GitHub Actions GitHub 내장판. Gitea Actions가 이걸 본떠
만들어서 사용법이 거의 같음
공통 원리: "이 저장소에 push가 오면 → 이 명령들을 순서대로 실행해"
라는 각본(워크플로) 파일로 적어 저장소에 같이 넣는다.
배포 방법 자체가 코드가 되고, 기록이 남고, 리뷰도 받는다!`;
const CODE_WORKFLOW_TASTE = `워크플로 파일 맛보기 — 외울 필요 없어요, "아 이런 느낌"만!
# .gitea/workflows/deploy.yml (우리 Gitea 기준)
on: push # push가 오면 깨어나라
jobs:
build-and-test:
steps:
- run: npm ci # 의존성 설치
- run: npm run build # 빌드 되나?
- run: npm test # 테스트 통과?
우리가 터미널에 손으로 치던 명령을
순서대로 받아 적은 것뿐이에요. 마법이 아닙니다!`;
const CODE_ROLLBACK = `롤백(Rollback) = 새 버전이 문제면 즉시 이전 버전으로 되돌리기
나쁜 시나리오 (롤백 준비 없음):
버전 배포 장애! "고칠 때까지 기다려 주세요" (사용자 이탈 ...)
좋은 시나리오 (롤백 준비 있음):
버전 배포 장애! 이전 버전으로 원복 (1) 사용자는 눈치
> 원인은 다음에 차분히 조사
롤백이 가능하려면:
이전 버전 보관 Docker 이미지에 v1.2, v1.3 같은 꼬리표(태그)
교체가 쉬운 구조 "v1.3 실행" "v1.2 실행"으로 명령만 바꾸면
배포 기록 언제 어떤 버전을 올렸는지 로그
게임의 세이브 포인트와 같아요. 보스전( 배포) 전에 저장해 두면,
져도(장애가 나도) 마을부터 다시 시작하지 않죠.`;
const CODE_PRACTICE_MAP = `실습 양식 — 우리 플랫폼 배포 지도 그리기
1단계: 현재 배포 순서를 화살표로 그린다 (섹션 1 참고, 빠짐없이!)
) 코드 수정 git push ??? ... 사용자가 화면을
2단계: 단계에 표시를 붙인다
[사람] 사람 손이 필요한 단계
[자동] 이미 자동인 단계
[위험] 실수하면 사이트가 죽는 단계
3단계: [사람]+[위험] 겹치는 단계에 친다
자동화 1순위! (실수 잦고 + 실수하면 아픈 곳부터)
4단계: 단계마다 "로봇이라면 어떤 명령을 칠까?" 줄씩 적는다
축하합니다. 방금 여러분이 적은 것이 워크플로 파일의 초안입니다.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '수동 배포의 고통' },
{ n: 2, label: 'CI — 자동 검사' },
{ n: 3, label: 'CD — 자동 배포' },
{ n: 4, label: '파이프라인 그림' },
{ n: 5, label: '도구들' },
{ n: 6, label: '롤백 한 입' },
{ n: 7, label: '실습: 자동화 지도' },
];
export default function CicdPage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 서버와 데이터</div>
<h1>CI/CD와 배포 자동화 입문</h1>
<p>
지금 우리 팀은 코드를 고칠 때마다 손으로 빌드하고, <strong>scp</strong> 서버에
올리고, 접속해서 재시작합니다 코스는 반복 노동을 <strong>로봇(파이프라인)</strong>에게
넘기는 방법을 다뤄요. 개념이 낯설어도 괜찮아요. 우리가 매주 겪는 배포 과정이
그대로 교재라서, 읽다 보면 "아 그거!" 하게 됩니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip"> 실습 2</span>
<span className="chip">준비물: 종이와 , Gitea 계정</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="수동 배포의 고통" sub="우리가 지금 하는 일, 솔직하게 공개">
<p>
"배포" 완성한 코드를 <strong>실제 서버에 올려 사용자가 있게 만드는 </strong>이에요.
그리고 지금 우리 플랫폼(AWESOMEDEV의 학습 사이트!) 일을 100% 사람 손으로 합니다.
숨기지 않고 과정을 공개할게요.
</p>
<Code>{CODE_MANUAL_DEPLOY}</Code>
<p>
한두 번은 만해요. 문제는 <strong>반복</strong>입니다. 급식실에서 배식을 매번
다른 사람이, 매번 다른 국자로, 매번 기억에 의존해서 한다고 상상해 보세요.
어떤 날은 양이 다르고, 어떤 날은 반찬 하나를 빼먹겠죠. 배포도 똑같아요
<strong> 사람이 반복하면 반드시 어딘가에서 실수가 납니다.</strong>
</p>
<Code>{CODE_HUMAN_ERROR}</Code>
<div className="warn">
<b>진짜 비용은 시간이 아니라 두려움</b> 배포가 무서우면 개발자는 배포를
미루게 되고, 미룰수록 번에 나가는 변경이 커져서 위험해지는 악순환에 빠져요.
자동화의 목표는 "배포가 <strong>지루할 만큼 안전한</strong> 일" 되게 하는 겁니다.
</div>
</Section>
<Section n={2} title="CI — 합칠 때마다 자동 검사" sub="Continuous Integration, 지속적 통합">
<p>
<strong>CI</strong> 코드가 저장소에 합쳐질 때마다(= push될 때마다) 로봇이
<strong> 자동으로 빌드하고 테스트</strong>하는 거예요. 공장 컨베이어 벨트 끝의
품질 검사 기계를 떠올리면 됩니다 제품이 지나갈 때마다 예외 없이, 같은 기준으로 검사하죠.
</p>
<Code>{CODE_CI_FLOW}</Code>
<p>
"합칠 때마다" 중요할까요? 버그는 <strong>일찍 잡을수록 싸게</strong> 고쳐져요.
방금 push한 코드가 빨간불이면 "아, 내가 아까 고친 그 부분이구나" 하고 바로 찾지만,
코드를 몰아서 검사하면 어디가 범인인지 수사부터 해야 하거든요.
시험 공부도 매일 조금씩 확인하는 쪽이 벼락치기보다 구멍을 빨리 찾는 것과 같아요.
</p>
<div className="tip">
<b>용어 정리</b> <span className="icode">Integration(통합)</span> 여러 사람의
코드를 저장소에 합치는 , <span className="icode">Continuous(지속적)</span>
"가끔 크게" 아니라 "자주 조금씩"이라는 뜻이에요. 합칠 때마다 검사하니까
<strong> 지속적 통합</strong>.
</div>
</Section>
<Section n={3} title="CD — 통과하면 자동 배포" sub="Continuous Deployment, 지속적 배포">
<p>
CI가 초록불을 켰다면, 다음은요? 검사까지 로봇이 놓고 배포는 다시 사람이
scp를 친다면 반쪽짜리죠. <strong>CD</strong> 검사를 통과한 코드를
<strong> 서버에 올리는 것까지 자동</strong>으로 이어 주는 겁니다.
</p>
<Code>{CODE_CD_FLOW}</Code>
<p>
섹션 1 7단계 수동 배포와 비교해 보세요. 사람이 하던 빌드·복사·재시작이 전부
로봇 몫이 되고, 사람에게 남는 일은 <span className="icode">git push</span> 하나예요.
좋은 <strong>기록</strong>이에요. 로봇은 언제, 누구의 커밋을, 어떤 검사를 거쳐
배포했는지 전부 로그로 남깁니다. "누가 언제 배포했지?"라는 질문이 사라져요.
</p>
<div className="tip">
<b>비슷한 구분</b> CD는 가지로 불려요.
<span className="icode">Continuous Delivery</span>(자동으로 배포 준비까지, 마지막
버튼은 사람이) vs <span className="icode">Continuous Deployment</span>(버튼까지 자동).
입문 단계에선 "통과하면 알아서 나간다" 묶어 이해해도 충분합니다.
</div>
</Section>
<Section n={4} title="파이프라인 — 푸시에서 배포까지 한 장의 그림" sub="push → 빌드 → 테스트 → 배포">
<p>
CI와 CD를 이어 붙인 전체 흐름을 <strong>파이프라인</strong>이라고 불러요.
그대로 파이프()예요 코드가 한쪽 (push)으로 들어가면 정해진 정거장들을
차례로 지나 반대쪽 (서비스 중인 서버)으로 나옵니다.
</p>
<Code>{CODE_PIPELINE}</Code>
<p>
가장 중요한 성질은 <strong>"실패하면 멈춘다"</strong>예요. 테스트가 깨진 코드는
배포 단계에 절대 도착하지 못합니다. 섹션 1 "테스트 없이 배포 서버에서 500 에러
폭발" 사고가 <strong>구조적으로 불가능</strong>해지는 거죠. 사람의 꼼꼼함을 믿는
아니라, 벨트의 구조가 불량품을 걸러내는 겁니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 종이에 파이프라인 그림을 보지 않고 다시 그려 보세요.
그리고 정거장 옆에 우리 스택의 실제 명령을 채워 넣어 보세요 ①번 옆에
<span className="icode">npm run build</span> <span className="icode">./mvnw package</span>,
③번 옆에 <span className="icode">docker</span>... 그림 장을 스스로 그릴 있으면
코스의 절반은 끝난 거예요.
</div>
</Section>
<Section n={5} title="도구들 — Gitea Actions와 Jenkins" sub="파이프라인을 굴려 주는 로봇들 (개념만!)">
<p>
파이프라인은 개념이고, 그걸 실제로 돌려 주는 <strong>도구</strong> 따로 있어요.
우리에게 제일 가까운 <strong>Gitea Actions</strong>입니다 매일 코드를 올리는
우리 Gitea(<span className="icode">edu.awesomedevapp.com:3000</span>) 이미 기능이
들어 있거든요.
</p>
<Code>{CODE_TOOLS}</Code>
<p>
도구는 달라도 원리는 하나예요. <strong>"push가 오면 이 명령들을 실행해"라는 각본을
파일로 써서 저장소에 넣는다.</strong> 각본을 워크플로(workflow)라고 불러요.
어떻게 생겼는지만 살짝 구경해 볼까요?
</p>
<Code>{CODE_WORKFLOW_TASTE}</Code>
<div className="tip">
<b>직접 확인해 보기</b> Gitea(<span className="icode">edu.awesomedevapp.com:3000</span>)
로그인해서 우리 저장소를 열고, 파일 목록에서 <span className="icode">.gitea/workflows/</span>
폴더가 있는지 찾아보세요. 있다면 안의 <span className="icode">.yml</span> 파일을 열어
"on:"(언제 깨어나나) "run:"(무슨 명령을 치나) 줄을 소리 내어 읽어 보세요.
아직 없다면? 그게 정상이에요 코스의 마지막 실습이 바로 파일의 설계도니까요.
</div>
</Section>
<Section n={6} title="배포 전략 한 입 — 롤백이 전부다" sub="새 버전이 망했을 때, 1분 안에 되돌리기">
<p>
자동 배포가 되면 끝일까요? 아니요 검사를 통과하고도 <strong>실제 서버에서만
터지는 문제</strong> 반드시 생겨요(진짜 사용자, 진짜 데이터, 진짜 트래픽은 테스트와
다르니까요). 그래서 프로 팀의 질문은 "장애가 날까?" 아니라
<strong> "장애가 났을 때 얼마나 빨리 되돌릴 수 있나?"</strong>입니다.
</p>
<Code>{CODE_ROLLBACK}</Code>
<p>
우리 스택에서 롤백의 열쇠는 <strong>Docker</strong>예요. 버전마다 이미지를 태그로
보관해 두면, 되돌리기는 "옛 이미지로 컨테이너 다시 실행" 줄이 됩니다. 서버에서
코드를 다시 고치는 아니라, <strong>통째로 포장된 이전 버전을 꺼내 오는 </strong>
이게 수동 시절엔 상상하기 어려웠던 속도의 비밀이에요.
</p>
<div className="warn">
<b>롤백 없는 자동 배포는 위험할 수도</b> 사람 배포는 느려서라도 조심하는데,
자동 배포는 잘못된 버전도 빛의 속도로 나갑니다. 그래서 자동화를 설계할
<strong> 나가는 (배포) 돌아오는 (롤백) 반드시 세트로</strong> 만들어야 해요.
</div>
</Section>
<Section n={7} title="실습 — 우리 플랫폼의 자동화 지도 그리기" sub="손으로 그려야 진짜 내 것이 됩니다">
<p>
이제 배운 전부 모아 <strong>진짜 설계</strong> 해볼 시간이에요. 도구 설치도,
코드 작성도 없어요. 필요한 종이와 , 그리고 섹션 1 우리 배포 과정입니다.
</p>
<Code>{CODE_PRACTICE_MAP}</Code>
<p>
실습이 중요한 이유: 현업에서 자동화가 실패하는 가장 원인은 도구를 몰라서가
아니라, <strong>자기 팀의 현재 과정을 정확히 모르는 </strong> 도구부터 붙이기
때문이에요. 지도를 먼저 그리는 사람이 길을 냅니다.
</p>
<div className="tip">
<b> 그렸다면</b> 완성한 지도를 멘토에게 보여 주고 가지를 물어보세요
"제가 그린 ★ 순서에 동의하시나요?", "제가 빠뜨린 단계가 있나요?"
멘토가 추가하는 단계(환경 변수 설정, DB 마이그레이션 같은)
바로 여러분이 아직 <strong>배포의 빙산 아랫부분</strong>입니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🤖 여기까지 왔다면</h3>
<p className="muted">
이제 여러분은 <strong>수동 배포의 고통 CI(자동 검사) CD(자동 배포)
파이프라인 롤백</strong>까지, 배포 자동화의 뼈대를 전부 갖췄어요. 파이프라인이
올라탈 무대가 궁금하다면 <Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스에서
서버까지 데이터가 가는 길을, 파이프라인이 실어 나르는 상자가 궁금하다면
Docker 관련 코스에서 컨테이너를 이어서 배워 보세요. 그리고 실습에서 그린
자동화 지도는 버리지 마세요 우리 플랫폼에 Gitea Actions를 실제로 붙이는 ,
종이가 워크플로 파일의 설계도가 됩니다.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,342 @@
// : " " ' '
// , , , , ,
// 7 .
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_REF_SITES = `3대 레퍼런스 사이트 — 각자 성격이 달라요
사이트 비유 여기서 얻는
드리블 미술관 전시장 완성도 높은 '한 장면' ·타이포·분위기
(dribbble) 실제 서비스가 아닌 컨셉 작업이 많음
비핸스 포트폴리오 발표회 프로젝트 '전체 과정' 문제 정의부터
(behance) 최종 화면까지 스토리로
핀터레스트 스크랩북 뭐든 모임 폭넓은 탐색과
(pinterest) '비슷한 이미지' 추천으로 꼬리물기
순서 추천: 핀터레스트로 넓게 훑고 드리블에서 완성도를 보고
비핸스에서 "어떻게 만들었는지" 과정을 읽어요.`;
const CODE_COPY_LINE = `베끼기와 참고의 경계 — 스스로에게 던지는 질문
베끼기 (하면 되는 )
- 남의 화면을 그대로 따라 그림 (트레이싱)
- ·레이아웃·아이콘·문구를 통째로 옮김
- "출처만 밝히면 되지 않나?" 됩니다. 출처 표기 사용 허락
참고 (해야 하는 )
- "이 화면은 왜 여백을 이렇게 줬을까?" 원리를 뽑아냄
- 여러 레퍼런스에서 원리를 모아 문제에 다시 적용
- 결과물을 나란히 놓았을 "영향은 보이지만 다른 작업물"
판별 테스트: 원본을 보고도 디자인을 다시 그릴 있는가?
YES 원리를 이해한 (참고)
NO 그림을 외운 (베끼기에 가까움)`;
const CODE_MOODBOARD = `피그마 무드보드 만들기 — 4단계
파일 만들기: Figma New design file 이름 "무드보드_학습플랫폼"
수집: 마음에 드는 화면을 스크린샷(Win+Shift+S) 피그마 캔버스에
Ctrl+V로 붙여넣기. 이미지 아래에 출처 URL을 텍스트로 함께!
태그 붙이기: 이미지 옆에 짧은 메모 텍스트
[색감] [여백] [카드UI] [버튼] [네비게이션] ...
나중에 "카드 디자인 참고할 거 어딨지?" 바로 찾게
묶기: 비슷한 태그끼리 Frame으로 그룹핑 + 프레임 이름 지정
) Frame "따뜻한 색감 모음", Frame "학습 카드 레이아웃"
핵심: 무드보드는 '전시' 아니라 '작업 도구'예요.
예쁘게 정리하는 시간 쓰지 말고, 빨리 찾을 있게만.`;
const CODE_ANALYSIS = `"왜 좋은가"를 언어로 바꾸는 분석 틀 — 한 장면당 3문장
1) 첫인상 단어: "정돈됨" / "발랄함" / "신뢰감" ...
2) 인상의 원인: UI/UX 코스에서 배운 용어로!
- 여백(화이트스페이스) 넉넉해서?
- 색이 2~3개로 절제돼서? (메인 컬러 + 포인트 컬러)
- 정렬(그리드) 맞아서? 위계( 제목작은 본문) 분명해서?
3) 프로젝트 적용: "우리 학습 카드에도 제목-부제 크기 차이를
키우면 위계가 살겠다"
예시 분석 (어느 학습 대시보드):
첫인상: "차분한 집중"
원인: 배경은 무채색, 진행률 바에만 초록 포인트
색의 절제가 시선을 '진행률' 모음
적용: 우리 코스 목록도 완료 표시에만 색을 주면 어떨까?`;
const CODE_ROUTINE = `나만의 수집 루틴 — 하루 10분이면 충분해요
매일 (10)
핀터레스트/드리블 피드 훑기 마음에 드는 2~3 저장
저장할 태그 단어라도 붙이기 (나중은 없다!)
매주 (30)
이번 모은 피그마 무드보드로 옮기기
그중 1 골라 '3문장 분석' 써보기 (섹션 5 !)
매달 (1시간)
무드보드 대청소 지나 봐도 설레는 삭제
"내 취향 지도" 갱신: 자꾸 모으게 되는 스타일이 뭔지 관찰
실력은 '많이 본 총량 × 분석한 횟수' 자랍니다.
모으기만 하고 분석 하면 그냥 스크랩 중독이에요.`;
const CODE_PRACTICE = `실습 과제 — 학습 플랫폼 개선 레퍼런스 5개 수집
목표: 지금 보고 있는 학습 플랫폼의 개선에 참고할
레퍼런스 5 + 각각 분석
제출 형식 (피그마 무드보드 1페이지):
[이미지1] 출처 URL
분석: "코스 카드에 썸네일+진행률을 함께 노출
우리 코스 목록에도 진행률 표시 추가하면 좋겠다"
[이미지2] 출처 URL
분석: ...
(× 5)
수집 힌트 이런 화면을 찾아보세요:
- 온라인 강의 플랫폼의 코스 목록 화면
- 학습 진행률/뱃지 같은 '동기부여 UI'
- 코드 예제가 들어간 문서/튜토리얼 페이지의 가독성
- 다크모드가 예쁜 개발자용 서비스
제출: 피그마 공유 링크를 멘토에게 전달 (보기 권한 확인!)`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '많이 본 사람이 이긴다' },
{ n: 2, label: '레퍼런스 사이트 투어' },
{ n: 3, label: '베끼기와 참고의 경계' },
{ n: 4, label: '무드보드 만들기' },
{ n: 5, label: '분석하며 보는 법' },
{ n: 6, label: '나만의 수집 루틴' },
{ n: 7, label: '실습: 플랫폼 개선 레퍼런스' },
];
export default function DesignRefsPage() {
return (
<div>
{/* 히어로: 이 코스가 무엇을 다루는지 */}
<div className="hero">
<div className="eyebrow">Course · 디자인</div>
<h1>레퍼런스와 무드보드<br /> 좋은 디자인을 '보는 눈' 기르기</h1>
<p>
좋은 디자인은 갑자기 머릿속에서 태어나지 않아요 <strong>좋은 것을 많이 보고,
좋은지 분석한 사람</strong>에게서 나옵니다. 코스에서는 레퍼런스를 모으고,
무드보드로 정리하고, 언어로 분석하는 습관을 만들어요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">준비물: 피그마 계정</span>
<span className="chip">실습 과제 1 제출</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="좋은 디자인은 많이 본 사람에게서 나온다" sub="눈의 수준이 손의 수준을 끌어올려요">
<p>
요리를 번도 먹어 사람이 맛있는 요리를 만들 있을까요? 디자인도 똑같아요.
<strong> 눈이 '좋은 것' 알아봐야, 손이 좋은 것을 만들 있습니다.</strong>
그래서 프로 디자이너들은 매일 남의 작업을 봐요 그걸 <strong>레퍼런스</strong>(reference,
참고 자료)라고 부릅니다.
</p>
<p>
처음엔 "내 눈은 이미 높은데 손이 못 따라간다" 답답한 시기가 와요. 이건 성장통이지
재능 문제가 아니에요. 눈과 손의 간격은 <strong>많이 보고 + 많이 만들면</strong> 반드시
줄어듭니다. 코스는 그중 '많이 보는 법' 제대로 배우는 시간이에요. 그냥 멍하니
스크롤하는 것과 <strong>수집하고 분석하며 보는 </strong> 완전히 다르거든요.
</p>
<div className="tip">
<b>미리 생각해 보기</b> 최근에 "이 앱 화면 예쁘다"라고 느낀 순간이 있나요?
그때 <strong></strong> 예쁘다고 느꼈는지 문장으로 말해 보세요. 지금 막히더라도
괜찮아요 섹션 5 끝나면 술술 말하게 거예요.
</div>
</Section>
<Section n={2} title="레퍼런스 사이트 투어" sub="드리블·비핸스·핀터레스트 — 목적에 맞는 곳으로">
<p>
디자이너들이 매일 들르는 3 사이트가 있어요. "디자인 구경하는 곳"이지만
성격이 달라서, <strong>언제 어디를 가느냐</strong> 알아야 시간 낭비가 없습니다.
</p>
<Code>{CODE_REF_SITES}</Code>
<p>
<strong>드리블</strong> 미술관이에요 장면 장면의 완성도가 높지만, 실제로
동작하는 서비스가 아닌 '보여주기용 컨셉' 많아요. 조합이나 분위기를 훔쳐보기(?) 좋죠.
<strong> 비핸스</strong> 발표회장 프로젝트가 "어떤 문제를, 어떤 과정을 거쳐,
어떻게 풀었는지" 스토리째로 올라와요. 기획을 배우기에 최고입니다.
<strong> 핀터레스트</strong> 스크랩북 하나 저장하면 "이것도 좋아할걸?" 하고
비슷한 계속 추천해 줘서, 취향의 꼬리를 물고 탐색하기 좋아요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 핀터레스트에서 <span className="icode">learning app UI</span>
검색해 보세요. 마음에 드는 화면 하나를 저장하고, 화면을 눌렀을 아래에 뜨는
"비슷한 이미지" 추천을 따라 3 꼬리물기 보세요 점점 취향에 가까운
화면이 나오는 느낄 있어요.
</div>
</Section>
<Section n={3} title="그대로 베끼기와 참고의 경계" sub="영감과 도둑질은 다릅니다">
<p>
레퍼런스를 모으다 보면 반드시 만나는 유혹 "이거 그냥 똑같이 만들면 안 되나?"
됩니다. 남의 디자인에도 <strong>저작권</strong> 있어요. 특히 남의 화면을 밑에 깔고
그대로 따라 그리는 <strong>트레이싱</strong>, 연습장 안에서만 허용되지
<strong> 제출물·포트폴리오·실제 서비스에 쓰는 순간 문제가 됩니다.</strong>
</p>
<Code>{CODE_COPY_LINE}</Code>
<div className="warn">
<b>저작권, 가볍게 보면 큰일나요</b> 아이콘·일러스트·폰트·사진은 각각 라이선스가
따로 있어요. "인터넷에서 주웠으니 공짜" 아닙니다. 회사 프로젝트에 무단으로 쓰면
<strong> 회사가 법적 책임</strong> 지게 돼요. 상업적 사용이 허용된 소스인지
라이선스를 반드시 확인하고, 애매하면 멘토에게 물어보세요.
</div>
<p>
요리로 비유하면 남의 레시피를 보고 <strong>"단맛과 매운맛의 균형이 비결이구나"라는
원리</strong> 배워 요리에 쓰는 참고예요. 남의 완성된 요리를 와서 접시에
담아 "내가 만들었어요" 하는 베끼기고요. 우리가 훔칠 것은 그림이 아니라
<strong> 원리</strong>입니다.
</p>
</Section>
<Section n={4} title="무드보드 만들기" sub="피그마에 모으고, 태그 붙이고, 묶기">
<p>
<strong>무드보드</strong>(moodboard) 프로젝트의 방향을 잡으려고 이미지··화면
레퍼런스를 판에 모아 놓은 보드예요. 요리사가 재료를 도마 옆에 늘어놓는
것과 같죠 만들기 전에 재료를 한눈에 봐야 메뉴가 정해지니까요. 우리는
<strong> 피그마</strong>(Figma) 만듭니다. 무료고, 링크 하나로 멘토와 공유되거든요.
</p>
<Code>{CODE_MOODBOARD}</Code>
<p>
가장 중요한 <strong> 태그</strong>예요. 태그 없는 무드보드는 책등에 제목 없는
도서관과 같아서, 100 모아도 정작 필요할 찾아요. 저장하는 순간에
단어라도 붙이는 습관 이게 무드보드의 절반입니다. 그리고 <strong>출처 URL</strong>
함께 섹션 3에서 배웠듯, 어디서 이미지인지 모르면 나중에 라이선스 확인도 해요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 바로 피그마에 파일을 만들고, 우리 학습 플랫폼의
지금 페이지를 <span className="kbd">Win</span>+<span className="kbd">Shift</span>+
<span className="kbd">S</span> 캡처해서 재료로 붙여 보세요. 태그도 하나
예를 들면 <span className="icode">[코스페이지]</span>. 축하해요, 무드보드 개장입니다!
</div>
</Section>
<Section n={5} title="분석하며 보는 법" sub="'예쁘다'를 '왜 좋은가'의 언어로 바꾸기">
<p>
"우와 예쁘다"에서 멈추면 실력이 늘어요. 축구 경기를 그냥 보는 관중과, "지금 수비
라인이 올라가서 뒷공간이 열렸네"라고 읽는 코치의 차이죠. 우리는 코치의 눈으로 봐야
합니다 그러려면 느낌을 <strong>언어로 번역</strong>해야 해요. 다행히 언어는 이미
배웠어요: UI/UX 코스에서 나온 <strong>여백·정렬·위계·색의 절제·일관성</strong> 같은
용어들이요.
</p>
<Code>{CODE_ANALYSIS}</Code>
<p>
3문장 틀이 처음엔 숙제 같지만, 번쯤 하면 화면을 보는 순간 자동으로 원인이
보이기 시작해요. 그때부터는 남의 디자인이 전부 <strong>공짜 교과서</strong> 됩니다.
특히 ③번( 프로젝트 적용) 빼먹지 마세요 적용 없는 분석은 반쪽짜리예요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 보고 있는 학습 플랫폼 페이지를 3문장 틀로 분석해
보세요. 첫인상 단어 원인(여백? 위계? ?) "내가 개선한다면?" 가지.
답이 바로 섹션 7 실습의 출발점이 됩니다.
</div>
</Section>
<Section n={6} title="나만의 수집 루틴" sub="하루 10분, 근육처럼 쌓이는 안목">
<p>
안목은 벼락치기가 돼요. 운동처럼 <strong>짧게, 자주, 꾸준히</strong> 답입니다.
시험 전날 몰아서 팔굽혀펴기 300 한다고 근육이 생기지 않듯이, 레퍼런스도 달에
3시간보다 <strong>매일 10</strong> 훨씬 강해요.
</p>
<Code>{CODE_ROUTINE}</Code>
<p>
매달 하는 "무드보드 대청소" 의외로 중요해요. 시간이 지나도 여전히 좋아 보이는 것만
남기다 보면, 남은 것들 사이에서 <strong> 취향의 공통점</strong> 보이기 시작하거든요.
그게 여러분의 <strong>디자인 정체성</strong> 씨앗입니다. 개발자의 별표(star) 저장소
목록이 사람의 관심사를 보여주듯이요.
</p>
<div className="tip">
<b>루틴 시작 </b> 거창하게 시작하지 마세요. 등하교 지하철에서 핀터레스트 5,
자기 전에 태그 정리 5 이미 하는 스크롤을 <strong>수집하는 스크롤</strong>
바꾸기만 하면 됩니다.
</div>
</Section>
<Section n={7} title="실습: 학습 플랫폼 개선 레퍼런스 5개" sub="이 플랫폼이 여러분의 첫 클라이언트">
<p>
이제 배운 전부 씁니다. 여러분이 매일 쓰는 <strong> 학습 플랫폼</strong>
클라이언트라고 생각하고, 개선에 참고할 레퍼런스 5개를 수집해 오세요. 플랫폼은
어썸데브가 여러분과 함께 계속 고쳐 나가는 <strong>살아 있는 교재</strong>라서,
좋은 제안은 실제로 반영될 있어요 진짜 실무 경험이 되는 거죠.
</p>
<Code>{CODE_PRACTICE}</Code>
<ol className="olist">
<li>섹션 2 순서대로 탐색 핀터레스트로 넓게, 드리블로 완성도, 비핸스로 과정.</li>
<li>후보를 10개쯤 모은 , 섹션 5 3문장 분석이 <strong> 써지는</strong> 5개만 남기기.</li>
<li>섹션 4 방식대로 피그마 무드보드에 정리 출처 URL과 태그 필수!</li>
<li> 이미지에 "우리 플랫폼에 이렇게 적용하면 좋겠다" 분석 달기.</li>
<li>공유 링크(보기 권한) 멘토에게 제출.</li>
</ol>
<div className="warn">
<b>주의</b> "예쁜 화면 5개" 아니라 <strong>"우리 플랫폼에 적용할 근거가 있는 5개"</strong>
찾는 과제예요. 분석 줄이 써지는 레퍼런스는 아무리 예뻐도 탈락입니다.
그리고 수집한 이미지를 그대로 옮겨 그리는 섹션 3에서 배운 대로 금지!
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🎨 여기까지 왔다면</h3>
<p className="muted">
이제 여러분은 디자인을 <strong>그냥 보는 사람</strong> 아니라
<strong> 수집하고 분석하는 사람</strong>이에요. 레퍼런스 사이트 3곳의 용도,
참고와 베끼기의 경계, 피그마 무드보드, 3문장 분석 , 그리고 하루 10 루틴까지
전부 손에 넣었습니다. 여기서 <strong>여백·위계·색의 절제</strong> 같은 용어가
아직 낯설다면 <Link to="/learn/uiux"><strong>UI/UX 기초</strong></Link> 코스를 먼저
다녀오세요. 그리고 섹션 7 실습 제출을 잊지 마세요 여러분의 무드보드가
플랫폼의 다음 버전을 만듭니다.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,375 @@
// : " GPU " CPU GPU
// , , AI GPU 7 .
// (GPU vs CPU vs ··AI GPU GPU )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_CPU_GPU = `CPU vs GPU — 코어 수와 일하는 방식이 다르다
CPU ( 명의 천재) GPU ( 명의 일꾼)
코어 4~24 (강력한 코어) 수천~수만 (단순한 코어)
잘하는 복잡한 판단, 순서 있는 똑같은 계산을 동시에 잔뜩
비유 수학 올림피아드 1 단순 덧셈만 하는 명의 알바
예시 "if면 A, 아니면 B" 분기 픽셀 200 동시에 계산
시험 문제 하나를 깊게 푸는 천재(CPU) 빠르지만,
덧셈 문제 백만 개를 채점하는 (GPU) 압도적으로 빨라요.`;
const CODE_FRAME = `화면 = 1초에 수십 번 다시 그려지는 그림 (프레임)
해상도: 장의 그림이 개의 (픽셀) 이뤄지는가
FHD (1920×1080) = 207 픽셀
QHD (2560×1440) = 369 픽셀
4K (3840×2160) = 829 픽셀 FHD의 4!
주사율(Hz): 모니터가 1초에 그림을 갈아 끼우는가
60Hz = 1초에 60 (일반 모니터)
144Hz = 1초에 144 (게이밍 모니터)
GPU의 = 프레임마다 픽셀 수백만 개의 색을 전부 계산하기
4K 60Hz라면? 829 픽셀 × 60 = 초당 5 픽셀!
이걸 코어 하나가 순서대로 하면 절대 끝내요.
그래서 수천 코어가 화면을 나눠 맡아 '동시에' 칠합니다.`;
const CODE_SPEC = `그래픽카드 스펙표, 이렇게 읽어요 (예: RTX 4060)
RTX 4060 8GB
VRAM 용량: GPU 전용 메모리. 텍스처·프레임·AI 모델이 사는
성능 등급: 자리가 클수록 상위 (4060 < 4070 < 4080)
세대: 자리가 세대 (30xx보다 40xx가 신형)
제품군 이름
VRAM은 GPU의 '작업 책상' 크기예요.
책상(VRAM) 작으면 재료를 창고(일반 RAM)에서
자꾸 가져와야 해서 아무리 손이 빨라도 느려집니다.
게임 고해상도 텍스처, AI 모델처럼 '재료가 큰' 작업일수록
VRAM 용량이 성능만큼 중요해요.
주의: 세대가 다르면 숫자만으로 비교 금지!
신형 xx60이 구형 xx70을 이기는 경우도 흔해요. (벤치마크 확인 필수)`;
const CODE_AI_GPU = `AI 시대, GPU가 금값이 된 이유
인공지능의 정체 = 사실상 거대한 행렬 곱셈(숫자표 × 숫자표) 덩어리
"단순한 곱셈·덧셈을 수십억 번" = GPU가 세상에서 제일 잘하는 !
원래 게임용으로 태어난 GPU가
게임: 픽셀 수백만 개를 동시에 계산 하는 일이
AI : 숫자 수십억 개를 동시에 계산 사실상 같아서
AI 학습의 표준 장비가 됐어요.
결과:
- 데이터센터용 GPU 가격 = 수천만
- 빅테크들이 GPU를 수십만 단위로 사재기
- "GPU 몇 장 확보했나" AI 회사의 경쟁력 지표가
(GPU) 파는 회사가 금광(AI) 붐의 최대 수혜자가 셈이죠.`;
const CODE_TASKMGR = `# 내 GPU 확인하기 — Windows 작업 관리자
1) Ctrl + Shift + Esc (작업 관리자 바로 열기)
2) 왼쪽 메뉴에서 [성능] 클릭
3) 목록 아래 GPU 0 (있다면 GPU 1) 클릭
읽어 :
오른쪽 이름 GPU 모델명 (: Intel UHD / RTX 3050)
전용 GPU 메모리 VRAM 용량 (섹션 4에서 배운 '작업 책상')
공유 GPU 메모리 부족할 빌려 쓰는 일반 RAM
3D / Video Decode 그래프 지금 GPU가 어떤 일을 하는 중인지
GPU 0 GPU 1 보인다면?
내장(CPU에 포함) + 외장(별도 카드) 같이 있는 PC!
평소엔 내장이 일하다가, 게임을 켜면 외장이 바빠지는
그래프로 직접 관찰할 있어요.`;
const CODE_VIDEO_TEST = `# GPU가 진짜 일하는 순간 목격하기
1) 작업 관리자 [성능] GPU 화면을 옆에 띄워 두고
2) 유튜브에서 4K 영상을 재생해 보세요
Video Decode 그래프가 올라갑니다
(영상 압축을 푸는 일을 GPU 전용 회로가 담당!)
3) 이번엔 브라우저로 우리 학습 플랫폼을 스크롤해 보세요
3D 그래프가 살짝 움직여요. 페이지 렌더링도
브라우저가 GPU에게 맡기고 있다는 증거!
"GPU는 게임할 때만 쓰는 것" 아니라는
숫자로 직접 확인하는 실습입니다.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: 'GPU vs CPU' },
{ n: 2, label: '화면이 그려지는 원리' },
{ n: 3, label: '내장 vs 외장' },
{ n: 4, label: '스펙 읽기' },
{ n: 5, label: '게임·영상·AI 속 GPU' },
{ n: 6, label: '개발자와 GPU' },
{ n: 7, label: '실습: 내 GPU 확인' },
];
export default function GpuPage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 컴퓨터 기초</div>
<h1>그래픽카드와 GPU의 세계</h1>
<p>
게임 한다는 친구들이 그렇게 그래픽카드에 목숨을 거는지, 그리고 요즘
AI 회사들이 GPU를 사재기하는지 이유를 원리부터 차근차근 알아봅니다.
마지막엔 <strong> PC의 GPU를 직접 열어 확인</strong>하는 실습까지 해볼 거예요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">실습 2</span>
<span className="chip">준비물: PC 하나</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="GPU vs CPU" sub="한 명의 천재 vs 만 명의 일꾼">
<p>
<strong>CPU</strong> 컴퓨터의 두뇌라고 배웠죠. 그럼 <strong>GPU</strong>(Graphics
Processing Unit, 그래픽 처리 장치) 뭘까요? 계산하는 칩인데,
<strong> 일하는 방식</strong> 정반대예요.
</p>
<p>
CPU는 <strong> 명의 천재</strong>예요. 아주 어려운 문제도 척척 풀고,
"이 경우엔 이렇게, 저 경우엔 저렇게" 같은 복잡한 판단을 순식간에 해내죠.
대신 몸은 하나(정확히는 )라서 번에 있는 일의 개수가 적어요.
반면 GPU는 <strong> 명의 일꾼</strong>이에요. 명은 단순한 덧셈·곱셈밖에
하지만, 명이 <strong>동시에</strong> 달려들면 단순 작업의 양에서는
천재 명이 도저히 따라옵니다.
</p>
<Code>{CODE_CPU_GPU}</Code>
<p>
그래서 컴퓨터는 둘을 <strong>역할 분담</strong>시켜요. 프로그램의 흐름과 판단은
CPU가, "화면 픽셀 200만 개 색칠하기"처럼 <strong>똑같은 계산이 대량으로</strong> 필요한
일은 GPU가 맡습니다. 어느 한쪽이 좋은 아니라 <strong>잘하는 일이 다른</strong> 거예요.
</p>
<div className="tip">
<b>기억 포인트</b> "복잡하고 순서가 중요한 일은 CPU, 단순하지만 양이 어마어마한 일은 GPU."
문장만 기억하면 코스의 절반은 끝났어요.
</div>
</Section>
<Section n={2} title="화면이 그려지는 원리" sub="프레임 · 해상도 · 주사율">
<p>
지금 보고 있는 화면은 사실 <strong>움직이지 않아요</strong>. 정지된 그림이
1초에 수십 번씩 갈아 끼워지고 있을 뿐이죠. 그림 장을 <strong>프레임</strong>이라고
하고, 애니메이션이 종이를 빠르게 넘겨 움직임을 만드는 것과 완전히 같은 원리입니다.
</p>
<Code>{CODE_FRAME}</Code>
<p>
여기서 GPU의 역할이 분명해져요. <strong>해상도</strong> 높을수록 장에 칠할 픽셀이
많아지고, <strong>주사율</strong> 높을수록 1초에 그려야 수가 늘어나요.
4K에 144Hz라면 초당 <strong>10 픽셀 이상</strong> 계산해야 하는데, 이게 바로
"만 명의 일꾼" 필요한 이유입니다. GPU가 프레임을 제때 만들어 내면 화면이
뚝뚝 끊기는 <strong>"프레임 드랍"</strong> 생기죠 게이머들이 그래픽카드에
집착하는 이유가 이거예요.
</p>
<div className="warn">
<b>흔한 오해</b> "모니터가 144Hz면 무조건 부드럽다"? 아니에요. GPU가 1초에 60장밖에
그리면 모니터가 144 갈아 끼울 그림이 없어요. <strong>모니터 주사율과 GPU 성능은
세트</strong> 맞아야 합니다.
</div>
</Section>
<Section n={3} title="내장 그래픽 vs 외장 그래픽" sub="같이 사는 세입자 vs 독채 사는 전문가">
<p>
GPU는 가지 형태로 존재해요. <strong>내장 그래픽</strong> CPU 안에
같이 들어 있는 작은 GPU예요. CPU라는 집에 <strong> 들어 사는</strong> 처지라
( 면적) 좁고, 전용 메모리 없이 일반 RAM을 <strong>빌려</strong> 씁니다.
<strong> 외장 그래픽</strong>(그래픽카드) <strong>독채</strong>예요. 자기만의 ,
자기만의 메모리(VRAM), 자기만의 냉각 팬까지 갖춘 별도의 부품이죠.
</p>
<p>그럼 선택해야 할까요? 기준은 의외로 간단해요.</p>
<ol className="olist">
<li>
<strong>문서 작업·웹서핑·코딩·영상 감상</strong> 대부분이다
내장으로 충분해요. 요즘 내장 그래픽은 4K 영상도 거뜬합니다.
</li>
<li>
<strong>3D 게임을 높은 옵션으로</strong> 하고 싶다 외장이 필요해요.
내장으로는 프레임이 나옵니다.
</li>
<li>
<strong>영상 편집·3D 모델링·AI 모델 돌리기</strong> 한다
외장, 그것도 VRAM이 넉넉한 걸로. (이유는 다음 섹션에서!)
</li>
</ol>
<p>
참고로 우리가 개발하는 학습 플랫폼(React 프론트 + Spring Boot 백엔드)
<strong> 내장 그래픽으로도 전혀 문제없이</strong> 개발할 있어요.
개발은 GPU보다 CPU와 RAM이 훨씬 중요합니다.
</p>
<div className="tip">
<b>노트북 </b> 외장 GPU가 달린 노트북은 성능이 좋은 대신
무겁고, 비싸고, 배터리가 빨리 닳아요. "혹시 게임할지도 모르니까" 사면
대부분 후회합니다. <strong>지금 확실히 하는 </strong> 기준으로 고르세요.
</div>
</Section>
<Section n={4} title="그래픽카드 스펙 읽기" sub="VRAM과 세대 — 숫자의 의미를 알면 광고에 안 속는다">
<p>
그래픽카드 이름은 암호처럼 보이지만, 규칙만 알면 <strong>세대</strong>
<strong> 등급</strong> 바로 읽혀요. 그리고 이름 뒤에 붙는 <strong>VRAM 용량</strong>
숨은 핵심입니다.
</p>
<Code>{CODE_SPEC}</Code>
<p>
<strong>VRAM</strong>(비디오 메모리) GPU 전용의 <strong>작업 책상</strong>이에요.
게임의 고화질 텍스처, 그리는 중인 프레임, AI 모델의 숫자들이 전부 책상 위에
올라가 있어야 GPU가 빠르게 작업할 있어요. 책상이 좁으면 창고(일반 RAM)
왔다 갔다 하느라 <strong>일꾼 명이 놀게</strong> 됩니다. 그래서 "연산 속도는 충분한데
VRAM이 부족해서 돌리는" 상황이 실제로 자주 생겨요.
</p>
<p>
<strong>세대</strong> 중요해요. 같은 등급이라도 세대가 다르면 설계 자체가 달라서,
신형 중급이 구형 상급을 이기기도 합니다. 숫자 크기만 보고 판단하지 말고,
실제 성능 측정 자료(벤치마크) 확인하는 습관을 들이세요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 인터넷 쇼핑몰에서 그래픽카드 아무거나 하나 검색해서
상세 페이지의 스펙표를 열어 보세요. 방금 배운 <span className="icode">세대</span>·
<span className="icode">등급</span>·<span className="icode">VRAM 용량</span> 가지를
직접 찾아 읽어 보면, 암호 같던 제품명이 문장처럼 읽히기 시작할 거예요.
</div>
</Section>
<Section n={5} title="게임 · 영상 편집 · AI에서 GPU가 하는 일" sub="그리고 AI 시대에 GPU가 금값이 된 이유">
<p>
<strong>게임</strong>에서 GPU는 프레임 3D 세계를 계산해요. 캐릭터의 위치,
빛이 반사되는 각도, 그림자의 방향까지 픽셀 하나하나가 전부 수학 계산의 결과입니다.
<strong> 영상 편집</strong>에서는 영상을 압축하고 푸는 (인코딩·디코딩)
효과 렌더링을 GPU가 맡아요. CPU만으로 1시간 걸릴 영상 내보내기가 GPU를 쓰면
분으로 줄어들죠.
</p>
<p>
그런데 진짜 반전은 <strong>AI</strong>예요. 게임용으로 태어난 GPU가
지금은 AI 산업 전체를 떠받치는 기둥이 됐거든요.
</p>
<Code>{CODE_AI_GPU}</Code>
<p>
AI 모델의 학습과 실행은 결국 <strong>거대한 숫자표끼리의 곱셈</strong> 수십억
반복하는 일이에요. "단순한 계산을 어마어마한 양으로" 어디서 들어본 말이죠?
바로 섹션 1 <strong> 명의 일꾼</strong> 가장 잘하는 일입니다. 그래서 세계
AI 회사들이 GPU를 확보하려고 줄을 서고, 데이터센터용 GPU는 부르는 값이 됐어요.
여러분이 쓰는 AI 챗봇의 답변 줄도, 지구 어딘가의 GPU 수천 장이 동시에
곱셈을 해서 만들어 결과물이에요.
</p>
</Section>
<Section n={6} title="개발자는 GPU가 언제 필요할까?" sub="솔직한 답: 웹 개발자는 거의 필요 없다">
<p>
그래서 개발자인 우리에겐 비싼 그래픽카드가 필요할까요? <strong>분야에 따라
완전히 달라요.</strong> 솔직하게 정리해 볼게요.
</p>
<ol className="olist">
<li>
<strong> 개발 (우리!)</strong> React·Spring Boot·PostgreSQL·Docker로 이루어진
우리 스택은 GPU를 거의 써요. 코드 컴파일, 여러 개의 Docker 컨테이너, 브라우저
수십 ... 전부 <strong>CPU와 RAM</strong> 일입니다. 내장 그래픽으로 충분해요.
</li>
<li>
<strong>게임 개발</strong> 자기가 만드는 게임이 돌아가는 봐야 하니
외장 GPU가 사실상 필수예요.
</li>
<li>
<strong>AI/머신러닝 개발</strong> 모델을 직접 학습시킨다면 VRAM GPU가
필요해요. 다만 요즘은 개인 PC 대신 <strong>클라우드의 GPU를 시간 단위로 빌려</strong> 쓰는
일반적이에요. 우리가 서버를 AWS EC2로 빌려 쓰는 것과 똑같은 방식이죠.
</li>
<li>
<strong>영상·3D 콘텐츠 제작</strong> 렌더링 시간이 작업 속도라
외장 GPU 투자 가치가 커요.
</li>
</ol>
<p>
핵심은 <strong>"내 작업이 CPU형인가 GPU형인가" 스스로 판단하는 </strong>이에요.
눈이 있으면 장비를 때도, 나중에 회사에서 서버 사양을 정할 때도
돈을 낭비하지 않게 됩니다.
</p>
<div className="tip">
<b>어썸데브 이야기</b> 우리 학습 플랫폼이 돌아가는 AWS EC2 서버에도 GPU가 없어요.
서비스는 GPU 없이 CPU·RAM만으로 돌아가는 대표적인 필요 없는 자원에
돈을 쓰지 않는 것도 엔지니어링입니다.
</div>
</Section>
<Section n={7} title="실습: 내 GPU를 직접 확인하자" sub="작업 관리자 성능 탭 탐험">
<p>
이론은 ! 이제 <strong> PC에 어떤 GPU가 있는지</strong> 직접 확인해 차례예요.
Windows에는 별도 프로그램 설치 없이 GPU를 관찰할 있는 도구가 이미 들어 있어요
바로 <strong>작업 관리자</strong>입니다.
</p>
<div className="tip">
<b>직접 확인해 보기 </b> 아래 순서대로 작업 관리자를 열고, GPU의
<strong> 모델명</strong> <strong>전용 GPU 메모리(VRAM)</strong> 찾아 적어 보세요.
섹션 4에서 배운 스펙 읽기를 PC에 그대로 적용하는 겁니다.
</div>
<Code>{CODE_TASKMGR}</Code>
<div className="tip">
<b>직접 확인해 보기 </b> 이번엔 GPU가 <strong>일하는 순간</strong> 실시간으로
목격해 봅시다. 그래프가 움직이는 보면 "GPU가 뭔가 하고 있다"
추상적인 지식이 아니라 눈에 보이는 사실이 돼요.
</div>
<Code>{CODE_VIDEO_TEST}</Code>
<div className="warn">
<b> PC에 GPU가 보여요?</b> GPU 항목이 하나뿐이고 이름에
<span className="icode">Intel</span>이나 <span className="icode">Radeon Graphics</span>
들어 있다면 내장 그래픽만 있는 PC예요. 고장이 아니라 아주 정상이고,
섹션 3에서 배웠듯 우리 개발 업무에는 그걸로 충분합니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🎮 여기까지 왔다면</h3>
<p className="muted">
이제 여러분은 CPU와 GPU의 역할 분담, 화면이 그려지는 원리, 스펙표 읽는 ,
그리고 AI 시대에 GPU가 금값인 이유까지 설명할 있어요. 무엇보다
<strong> "이 작업엔 어떤 자원이 필요한가" 판단하는 </strong> 생겼습니다.
다음은 자원 위에서 데이터가 어떻게 오가는지 {' '}
<Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스에서
랜선 가닥부터 인터넷까지 이어서 배워 보세요. 실습에서 확인한 GPU
모델명과 VRAM 용량은 멘토와의 다음 체크인 공유해 주세요!
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,357 @@
// : " "
// ( ), ( )
// 7 .
// ( BIOS/UEFI ·80PLUS )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_BOARD_MAP = `메인보드 = 부품들이 사는 도시의 도로망 (위에서 내려다본 지도)
[후면 포트] [CPU 소켓] [RAM 슬롯 ×4]
USB· 시청(CPU) 앉는 자리
[칩셋] 교통관제센터
[PCIe 슬롯] 그래픽카드 고속도로
[M.2 슬롯] SSD가 눕는 자리
[SATA 포트] 옛날 방식 저장장치
[24 전원 커넥터] 파워
칩셋(chipset) 하는 : CPU가 직접 상대하기 귀찮은
USB·SATA· 같은 '변두리 교통' 대신 정리해 주는 관제센터.
그래서 메인보드 이름(B650, Z790...) 칩셋 이름이에요.`;
const CODE_FORM_FACTOR = `폼팩터 = 메인보드의 '옷 사이즈' 규격
이름 크기(대략) 비유 특징
ATX 305×244mm 평수 아파트 슬롯 많음, 확장 자유
mATX 244×244mm 국민 평수 가성비 조립의 단골
Mini-ITX 170×170mm 원룸 작고 예쁨, 확장 포기
케이스와 짝이 맞아야 해요. 원룸(ITX 케이스)
대형 소파(ATX 보드) 들어갑니다. 반대로
케이스에 작은 보드는 OK (나사 구멍이 호환됨).`;
const CODE_REAR_PANEL = `후면 패널(I/O 패널) 해부 — 본체 뒤를 보면 이런 게 모여 있어요
PS/2(구형 키보드·마우스, 요즘은 드묾)
USB-A ×4 파랑=3.x(빠름) 검정=2.0
USB-C 방향 없이 꽂는 요즘 표준
HDMI / DP 내장그래픽용 화면 출력
LAN(RJ45) 네트워크 코스에서 랜선!
오디오 3.5mm 초록=출력 분홍=마이크
주의: 그래픽카드를 꽂았다면 모니터는 보드의 HDMI가 아니라
그래픽카드 HDMI/DP에 꽂아야 해요. (단골 실수 1!)`;
const CODE_BIOS_ROLE = `BIOS/UEFI = 학교의 '아침 조회 담당 선생님'
전원 버튼을 누르면 (OS가 뜨기 전에) 벌어지는 :
1) POST 출석 체크
"CPU 있나? RAM 있나? 키보드 있나?" 부품 점호
문제가 있으면 - 소리(비프음) LED로 알림
2) 부팅 순서 결정
"오늘 수업(OS)은 어느 저장장치에서 시작하지?"
SSD USB 네트워크... 순서를 여기서 정함
3) OS에게 바통 터치
Windows/리눅스 부트로더를 불러오고 자기는 퇴장
BIOS(구형) vs UEFI(신형):
UEFI는 마우스 되는 그래픽 화면 + 2TB 넘는 디스크 지원 +
빠른 부팅. 요즘 보드는 전부 UEFI지만 습관적으로 "바이오스" 불러요.`;
const CODE_BIOS_ENTER = `UEFI(BIOS) 화면 들어가 보기
1) PC를 재부팅한다
2) 제조사 로고가 뜨는 짧은 순간에 키를 연타:
Del 또는 F2 (제조사마다 다름 로고 화면 구석에 표시돼요)
3) 파란/회색 설정 화면이 뜨면 성공!
구경 포인트 (설정은 저장하지 말고 구경만):
- CPU 온도· 속도가 실시간으로 보이는 모니터링 화면
- Boot 순서 (어느 디스크에서 OS를 시작하는지)
- 나갈 때는 "Exit Without Saving" 저장 없이 탈출!`;
const CODE_PSU_WATT = `파워(PSU)의 정격 출력 — '정격'이라는 두 글자가 핵심
정격 500W = "500W를 계속, 안정적으로 낼 수 있다" 보증
최대 500W = "순간적으로 500W까지는 가능(계속은 무리)"라는 말장난
싸구려 파워가 자주 쓰는 눈속임이 '최대 출력' 크게 적는 .
반드시 '정격(rated)' 기준으로 고르세요.
우리 부품은 얼마나 먹을까? (대략)
CPU 65~150W
그래픽카드 100~350W 최대 대식가
보드+RAM+SSD 50W 안팎
합계의 1.5~2 여유를 정격 보통 600~750W면 넉넉
80PLUS 등급 = 전기를 얼마나 낭비 없이 변환하나 (효율 인증)
Standard < Bronze < Silver < Gold < Platinum < Titanium
Gold(효율 ~90%): 벽에서 100W 끌어와 90W를 부품에 전달, 10W는 열로 손실
등급이 높을수록 전기요금 발열 소음`;
const CODE_MSINFO = `# 내 PC 메인보드 모델 확인하기 (Windows)
# 방법 1 시스템 정보 :
# Win+R msinfo32 입력 Enter
# "시스템 요약"에서 항목들을 찾으세요:
베이스보드 제조업체 : ASUSTeK COMPUTER INC. 보드 제조사
베이스보드 제품 : PRIME B650M-A 이게 모델명!
BIOS 버전/날짜 : ... 1234, 2025-11-02 UEFI 버전
BIOS 모드 : UEFI 섹션 4에서 배운 그것
# 방법 2 터미널파:
wmic baseboard get manufacturer, product
# 모델명(: B650M)에서 섹션 1 '칩셋'
# 섹션 2 폼팩터(M이 붙으면 mATX인 경우가 많아요)까지 읽어낼 있어요.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '메인보드 지도' },
{ n: 2, label: '폼팩터' },
{ n: 3, label: '후면 포트' },
{ n: 4, label: 'BIOS/UEFI' },
{ n: 5, label: '파워 정격·80PLUS' },
{ n: 6, label: '파워가 심장인 이유' },
{ n: 7, label: '실습: 내 보드 확인' },
];
export default function MainboardPowerPage() {
return (
<div>
{/* 히어로: 이 코스가 다루는 두 주인공 소개 */}
<div className="hero">
<div className="eyebrow">Course · 컴퓨터 기초</div>
<h1>메인보드와 파워 깊이 보기<br /> 도시의 도로망과 심장</h1>
<p>
CPU·RAM·그래픽카드가 스타플레이어라면, 메인보드는 그들이 뛰는 <strong>경기장이자
도로망</strong>이고 파워는 경기장 전체에 피를 돌리는 <strong>심장</strong>이에요.
눈에 띄지만 둘을 잘못 고르면 어떤 좋은 부품도 실력을 냅니다
그런지 지도를 펴고 하나씩 따라가 봅시다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">실습 2</span>
<span className="chip">준비물: PC 하나</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="메인보드 = 도시의 도로망" sub="칩셋·소켓·슬롯, 지도부터 익히기">
<p>
컴퓨터를 도시라고 생각해 보세요. CPU는 시청, RAM은 시청 바로 창고,
그래픽카드는 대형 공장, SSD는 물류센터예요. 그런데 이들이 아무리 훌륭해도
<strong> 서로 연결하는 도로</strong> 없으면 도시는 돌아가지 않죠.
도로망 전체가 <strong>메인보드</strong>입니다. 초록색 기판 위에 보이는
가느다란 금색 하나하나가 실제로 데이터가 달리는 도로(회로)예요.
</p>
<Code>{CODE_BOARD_MAP}</Code>
<p>
지도에서 위치 감각만 잡으면 됩니다. <strong>CPU 소켓</strong> 보드마다 규격이
달라서(인텔 LGA1700, AMD AM5 ) CPU와 짝이 맞아야 하고, <strong>RAM 슬롯</strong>
보통 2~4, <strong>PCIe 슬롯</strong> 그래픽카드가 꽂히는 왕복 16차선
고속도로(x16)예요. 요즘 SSD는 납작한 <strong>M.2 슬롯</strong> 눕혀 나사 하나로
고정합니다 케이블이 아예 없어요.
</p>
<div className="tip">
<b>비유 정리</b> 메인보드를 고른다 = 도시의 도로 계획을 고른다.
어떤 시청(CPU) 들어올 있고, 공장(확장카드) 지을 있는지가
여기서 정해져요.
</div>
</Section>
<Section n={2} title="폼팩터 — ATX, mATX, ITX" sub="보드에도 옷 사이즈가 있다">
<p>
메인보드는 아무 크기로나 만들지 않아요. 케이스·파워와 서로 조립될 있도록
세계가 약속한 <strong>표준 사이즈</strong> 있는데, 이걸
<strong> 폼팩터</strong>(form factor)라고 부릅니다. 옷의 S/M/L 사이즈 같은 거예요.
</p>
<Code>{CODE_FORM_FACTOR}</Code>
<p>
크기가 작아질수록 슬롯 수가 줄어듭니다. ATX는 RAM 4·PCIe 여러 개로 확장이
자유롭고, Mini-ITX는 RAM 2·PCIe 1개가 한계예요. 대신 책상 위에 올려도
예쁜 초소형 PC를 만들 있죠. <strong>"확장성 vs 크기" 트레이드오프</strong>
개발에서도 계속 만나게 , 공학의 영원한 저울질입니다.
</p>
<div className="warn">
<b>조립 체크</b> 케이스 상세 페이지엔 반드시 "지원 폼팩터: ATX, mATX..."
적혀 있어요. 보드를 먼저 고르고 케이스가 사이즈를 지원하는지 확인
택배 왔는데 들어가는 참사가 실제로 흔합니다.
</div>
</Section>
<Section n={3} title="포트 한눈에 — 후면 패널 해부" sub="본체 뒤편은 도시의 관문(항구·공항)">
<p>
메인보드에서 유일하게 케이스 밖으로 노출되는 부분이 <strong>후면 I/O 패널</strong>이에요.
도시로 치면 바깥세상과 물자를 주고받는 항구와 공항이죠. 여기 뭐가 있는지 알면
"이 케이블 어디 꽂아요?"라는 질문의 90% 해결됩니다.
</p>
<Code>{CODE_REAR_PANEL}</Code>
<p>
USB 포트의 <strong>색깔</strong> 장식이 아니에요. 파란색(USB 3.x) 검은색(2.0)보다
이론상 10 이상 빨라서, 외장 SSD는 파란 포트에 꽂아야 속도가 나옵니다.
<span className="icode">LAN(RJ45)</span> 포트는 <Link to="/learn/network">네트워크의 이해</Link> 코스에서
만든 랜선이 꽂히는 코스끼리 이렇게 연결돼요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 쓰는 PC(학교 실습실 PC도 좋아요) 본체 뒤를 살펴보세요.
USB 파란 포트와 검은 포트를 구분해 보고, 모니터 케이블이 보드 쪽에 꽂혀 있는지
그래픽카드 쪽에 꽂혀 있는지 확인해 보세요. 노트북이라면 옆면 포트들로 같은 관찰을!
</div>
</Section>
<Section n={4} title="BIOS/UEFI가 하는 일" sub="OS보다 먼저 깨어나는 조회 담당 선생님">
<p>
전원 버튼을 누르면 Windows가 바로 뜨는 아니에요. 전에 메인보드에 박힌
작은 프로그램, <strong>BIOS/UEFI</strong> 먼저 깨어납니다.
아침 조회 시간에 담임 선생님이 출석을 부르고 나서야 수업(OS) 시작되는 것처럼요.
</p>
<Code>{CODE_BIOS_ROLE}</Code>
<p>
그래서 부팅이 정비사들이 제일 먼저 확인하는 단계예요.
POST에서 멈추면 "OS 문제가 아니라 <strong>부품(하드웨어) 문제</strong>"라는
강력한 힌트가 되거든요. 소프트웨어를 의심하기 전에 하드웨어 점호 결과부터
디버깅의 기본자세는 여기서도 똑같습니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 아래 순서대로 UEFI 화면에 직접 들어가 보세요.
, <strong>아무것도 저장하지 않고 나오기</strong> 오늘의 규칙입니다.
</div>
<Code>{CODE_BIOS_ENTER}</Code>
<div className="warn">
<b>주의</b> UEFI에서 설정을 바꾸고 저장하면 부팅이 수도 있어요.
구경은 자유, 저장은 금지. 학교·회사 공용 PC라면 들어가기 전에 담당자(멘토)에게
먼저 물어보는 매너입니다.
</div>
</Section>
<Section n={5} title="파워의 정격 출력과 80PLUS 등급" sub="와트(W) 숫자와 금·은·동메달 읽는 법">
<p>
이제 도시에 전기를 넣을 차례예요. <strong>파워 서플라이(PSU)</strong> 콘센트의
교류 220V를 부품들이 먹는 직류 12V·5V·3.3V로 바꿔서 나눠 주는
<strong> 변전소이자 심장</strong>입니다. 파워를 고를 보는 숫자는 가지
<strong> 정격 출력(W)</strong> <strong>80PLUS 등급</strong>이에요.
</p>
<Code>{CODE_PSU_WATT}</Code>
<p>
넉넉하게 잡을까요? 파워는 <strong>절반쯤 부하일 효율이 가장 좋고</strong> 수명도
길어요. 사람도 전력 질주를 계속하면 금방 지치듯, 파워도 한계치 근처에서 계속 돌면
뜨거워지고 빨리 늙습니다. 그리고 나중에 그래픽카드를 업그레이드할 여유분이기도 하고요.
</p>
<div className="tip">
<b>등급의 진짜 의미</b> 80PLUS는 "좋은 파워 인증"이라기보다 <strong>효율 인증</strong>이에요.
다만 효율을 높이려면 좋은 부품을 써야 하니, 결과적으로 Gold 이상 제품이
품질도 좋은 경향이 있는 거죠. 상관관계와 인과관계의 차이 데이터 때도 중요한 감각!
</div>
</Section>
<Section n={6} title="파워가 싸구려면 안 되는 이유" sub="심장이 멈추면 도시 전체가 멈춘다">
<p>
조립 견적에서 예산이 부족하면 제일 먼저 깎고 싶어지는 파워예요.
성능 벤치마크에 나오니까요. 하지만 정비 경험자들이 입을 모아 말리는 데는
이유가 있습니다. 파워는 <strong>모든 부품과 전선으로 연결된 유일한 부품</strong>이거든요.
</p>
<p>
CPU가 고장 나면 CPU만 바꾸면 돼요. 그런데 싸구려 파워가 <strong>과전압을 흘리며
죽으면</strong>, 전기를 받아먹던 메인보드·SSD·그래픽카드까지 <strong>같이 데려갈</strong>
있어요. 심장이 멈추면 장기가 아니라 온몸이 위험해지는 것과 같죠.
10 아끼려다 100 원어치 부품을 태우는, 가성비가 가장 나쁜 절약입니다.
</p>
<div className="olist">
<ol>
<li><strong>보호 회로의 유무</strong> 좋은 파워엔 과전압(OVP)·과전류(OCP)·단락(SCP)
보호가 들어 있어 사고가 나도 스스로 꺼지며 다른 부품을 지켜요. 싸구려는 이걸 생략합니다.</li>
<li><strong>표기 뻥튀기</strong> 섹션 5에서 '최대 출력' 눈속임. 정격 300W짜리에
"600W"라고 크게 적어 파는 제품이 실제로 있어요.</li>
<li><strong>조용한 증상</strong> 죽기 전엔 게임 갑자기 꺼짐, 이유 없는 재부팅 같은
애매한 증상으로 나타나서 원인 찾기도 어렵습니다.</li>
</ol>
</div>
<div className="warn">
<b>기억할 </b> "파워에 쓰는 돈은 성능이 아니라 <strong>보험</strong>이다."
견적 전체의 10~15% 파워에 배정하고, 검증된 제조사의 정격·80PLUS 인증 제품을 고르세요.
</div>
</Section>
<Section n={7} title="실습 — 내 PC 메인보드 모델 확인" sub="msinfo32로 오늘 배운 걸 전부 확인하기">
<p>
마지막 실습은 케이스를 열지 않고도 메인보드의 정체를 알아내는 방법이에요.
Windows에 내장된 <span className="icode">msinfo32</span>(시스템 정보) 쓰면
제조사·모델명·UEFI 버전까지 화면에 나옵니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> <span className="kbd">Win</span>+<span className="kbd">R</span>
눌러 실행 창을 열고 <span className="icode">msinfo32</span> 입력한 {' '}
<span className="kbd">Enter</span>. 아래 항목들을 찾아 적어 보세요.
</div>
<Code>{CODE_MSINFO}</Code>
<div className="olist">
<ol>
<li>찾은 모델명을 검색해서 제조사 공식 페이지를 열어 보세요.</li>
<li>스펙 표에서 <strong>폼팩터</strong>(섹션 2), <strong>RAM 슬롯 </strong>(섹션 1),
<strong> 후면 포트 구성</strong>(섹션 3) 확인 오늘 배운 지도가 실제 스펙 표를
읽는 안경이 됩니다.</li>
<li>"BIOS 모드" UEFI로 나오는지도 확인해 보세요(섹션 4).</li>
</ol>
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🔌 여기까지 왔다면</h3>
<p className="muted">
이제 메인보드가 부품들의 <strong>도로망</strong>이고 파워가 <strong>심장</strong>이라는
, 지도와 숫자(정격 W·80PLUS) 읽을 있게 됐어요. 우리가 매일 쓰는 AWS EC2
서버도 결국 어딘가의 데이터센터에서 이런 보드와 파워로 돌아가는 진짜 컴퓨터입니다.
다음은 컴퓨터들을 서로 잇는 {' '}
<Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스로 이어가거나,
멘토와 함께 <strong>실습실 PC 대를 직접 열어 오늘 지도를 실물로 대조</strong>하는
과제에 도전해 보세요.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,420 @@
// : " " ' '
// , given-when-then , ProgressServiceTest.java ,
// , , ,
// 8 .
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_MANUAL_VS_AUTO = `수동 테스트 vs 자동 테스트 — 기능이 30개일 때
수동 테스트 자동 테스트
30 화면을 매번 손으로 클릭 명령 () 전부 실행
바퀴 도는 30+ ~ 수십
피곤하면 3~4개는 건너뜀 하나도 빼먹음
"아마 되겠지" 하고 배포 초록불을 보고 배포
버그를 사용자가 먼저 발견 버그를 테스트가 먼저 발견
코드를 고칠 때마다 차이가 반복해서 쌓입니다.`;
const CODE_GWT = `// 단위 테스트의 3막 구조 — given / when / then
@Test
void 강의를_완료하면_진도율이_올라간다() {
// given( ):
Student student = new Student("김미림");
Course course = new Course("네트워크의 이해", 10); // 10
// when( ):
student.complete(course.lesson(1));
// then( ):
assertThat(student.progressOf(course)).isEqualTo(10); // 10%
}
// .
// given = , when = , then = .
// 3 .`;
const CODE_PROGRESS_TEST = `// backend/src/test/java/.../ProgressServiceTest.java 中
@Test
void 이미_완료한_강의를_다시_완료해도_진도율은_그대로다() {
// given: 10 1
Progress progress = Progress.of(studentId, courseId, totalLessons(10));
progress.completeLesson(1L);
// when: 1 ( !)
progress.completeLesson(1L);
// then: 10% 20%
assertThat(progress.percent()).isEqualTo(10);
assertThat(progress.completedCount()).isEqualTo(1);
}`;
const CODE_PROGRESS_ANATOMY = `이 테스트 한 편을 해부해 보면:
메서드 이름이 한글 문장
"이미 완료한 강의를 다시 완료해도 진도율은 그대로다"
테스트 목록만 읽어도 명세서(스펙) 됩니다.
new / of DB,
그래서 테스트는 0.01 만에 끝나요. (단위 테스트!)
검증(assert) 핵심 시나리오를 정확히 겨냥
percent() 10인지 + completedCount() 1인지, 각도에서 확인.
그리고 테스트, 실화 기반입니다.
완료 버튼을 연타하면 진도율이 2배로 오르던 버그가 실제로 있었고,
고친 "다시는 재발하지 마라" 박아 테스트예요.
테스트는 버그의 묘비명이자, 부활 방지 부적입니다.`;
const CODE_FIRST = `좋은 단위 테스트의 조건 — 특히 이 두 가지
빠르다 (Fast)
전체 테스트가 커피 모금 마시기 전에 끝나야
"고칠 때마다 돌리는" 습관이 생깁니다.
5 걸리면? 아무도 돌리고, 돌리는 테스트는 없는 테스트예요.
독립적이다 (Independent)
테스트끼리 순서·데이터를 공유하면 됩니다.
A가 만든 데이터를 B가 쓰면 A만 지워도 B가 터지는
"도미노 테스트" 돼요. 테스트는 자기 무대(given)
자기가 차리고, 혼자 돌려도 통과해야 합니다.
: 반복 가능(어제도 오늘도 같은 결과),
자가 검증(사람이 로그를 눈으로 봐도 초록/빨강으로 판정).`;
const CODE_BOUNDARY = `경계값 분석 — 버그는 '가장자리'에 삽니다
진도율(0~100%) 테스트한다면?
47% 같은 어중간한 1개보다, 아래 가장자리들이 훨씬 잡아요:
노리는 버그
강의 0 완료 0% 0으로 나누기 에러는 없나?
1 완료 1 반올림이 이상하지 않나?
전부 완료 정확히 100 99.999% 끝나 버리진 않나?
전체 강의 0개인 코스 분모가 0! 제일 위험한 손님
같은 강의 2 완료 중복 처리 (아까 실화)
수학 시험과 같아요 함정 문제는 x=0, 경계 바로 안팎에서 나오죠.
테스트 케이스를 고를 때도 "정상적인 가운데" 말고 "아슬아슬한 끝" 노리세요.`;
const CODE_REFACTOR = `테스트가 있을 때 vs 없을 때 — 리팩터링 풍경
테스트 없음 테스트 있음
" 코드 지저분한데... 과감하게 구조를 갈아엎음
건드렸다 터지면 어쩌지"
테스트 실행 (10)
결국 건드림
초록불 안심하고 커밋
지저분한 코드 위에 빨간불 ", 케이스를
지저분한 코드가 쌓임 깜빡했네" 하고 즉시 수정
테스트는 안전그물입니다. 그물이 있어야
공중제비(리팩터링) 시도할 용기가 생겨요.`;
const CODE_RUN_TESTS = `# 우리 백엔드 테스트 전체 실행 — 두 가지 방법
# 방법 1) IntelliJ에서 (추천)
# backend/src/test/java 폴더에서 마우스 오른쪽 클릭
# "Run 'All Tests'" 또는 테스트 클래스 옆의 초록 버튼 클릭
# 아래 실행 창에 초록 체크()들이 주르륵!
# 방법 2) 터미널에서 (backend 폴더에서)
./gradlew test
# 이런 결과가 보이면 성공:
ProgressServiceTest > 이미_완료한_강의를_다시_완료해도_진도율은_그대로다 PASSED
ProgressServiceTest > 모든_강의를_완료하면_진도율은_정확히_100이다 PASSED
...
BUILD SUCCESSFUL`;
const CODE_QA02 = `# 도전: 테스트 케이스 1개 추가하기 (티켓 QA-02)
# ProgressServiceTest.java 아직 없는 경계값 케이스:
# "강의가 0개인 코스의 진도율을 물으면?"
# 0으로 나누기 폭발 없이, 얌전히 0% 나와야 한다.
@Test
void 강의가_하나도_없는_코스의_진도율은_0이다() {
// given: 0 ( 0 !)
Progress progress = Progress.of(studentId, courseId, totalLessons(0));
// when + then: 0
assertThat(progress.percent()).isEqualTo(0);
}
# 테스트를 추가하고 실행
# 초록불: 이미 방어돼 있던 . 축하해요, 명세 하나를 문서화했습니다.
# 빨간불: 진짜 버그를 찾은 겁니다! 멘토와 함께 고쳐 보세요.
# 완료하면 Gitea(edu.awesomedevapp.com:3000) QA-02 티켓에
# 결과 스크린샷과 함께 코멘트를 남겨 주세요.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '테스트 = 보험' },
{ n: 2, label: '수동 테스트의 한계' },
{ n: 3, label: 'given-when-then' },
{ n: 4, label: '실제 테스트 해부' },
{ n: 5, label: '좋은 테스트의 조건' },
{ n: 6, label: '경계값을 노려라' },
{ n: 7, label: '리팩터링과 테스트' },
{ n: 8, label: '실습: 직접 돌려 보기' },
];
export default function TestingPage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 소프트웨어</div>
<h1>테스트 코드 입문<br /> 미래의 나를 지키는 보험 들기</h1>
<p>
"코드가 잘 돌아가는지 매번 손으로 눌러 보는" 세계에서 벗어나,
컴퓨터가 대신 검사해 주는 <strong>테스트 코드</strong> 세계로 들어갑니다.
마지막엔 우리 플랫폼 백엔드의 <strong>진짜 테스트를 직접 실행</strong>하고,
케이스 하나를 추가하는 것까지 봅니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 70</span>
<span className="chip">실습 2 + 도전 과제 1</span>
<span className="chip">준비물: IntelliJ + 우리 backend 코드</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="테스트는 미래의 나를 지키는 보험" sub="지금 5분 vs 나중의 밤샘">
<p>
테스트 코드란 <strong>"내 코드가 약속대로 동작하는지 검사하는 또 다른 코드"</strong>예요.
진도율 계산 함수를 만들었다면, "강의 10개 중 1개 완료 → 10%가 나와야 해"라고
검사하는 코드를 옆에 하나 두는 거죠.
</p>
<p>
이게 <strong>보험</strong>이냐면 보험은 사고가 <strong>나기 전에</strong> 들잖아요.
코드도 마찬가지예요. 지금은 멀쩡해 보여도, 3개월 뒤의 (혹은 동료)
코드를 고치다가 나도 모르게 뭔가를 부술 있어요. 그때 테스트가 있으면
<strong> 빨간불이 켜지며 즉시 알려 주고</strong>, 없으면 사용자가 "진도율이 이상해요"
하고 신고할 때까지 아무도 모릅니다.
</p>
<p>
그리고 보험처럼, 보험료(테스트 작성 시간) 지금 조금씩 내지만
사고가 났을 돌려받는 보험금(밤샘 디버깅 면제) 훨씬 큽니다.
</p>
<div className="tip">
<b>용어 정리</b> 코스에서 다루는 <strong>단위 테스트</strong>(unit test)
함수·클래스 같은 <span className="icode">작은 부품 하나</span> 떼어 검사하는 테스트예요.
우리 Spring Boot 백엔드에서는 <span className="icode">JUnit</span>이라는 도구로 작성합니다.
</div>
</Section>
<Section n={2} title="수동 테스트의 한계" sub="매번 다 눌러볼 수는 없다">
<p>
"나 테스트 했는데?" 맞아요, 브라우저 열고 버튼 눌러 보는 것도 테스트예요.
<strong> 수동 테스트</strong>. 기능이 3개일 이걸로 충분합니다.
문제는 기능이 30, 300개가 때예요.
</p>
<Code>{CODE_MANUAL_VS_AUTO}</Code>
<p>
무서운 <strong>"고친 곳 말고 딴 데가 터지는"</strong> 경우예요.
진도율 코드를 고쳤는데 출석 기능이 망가진다든가. 수동 테스트로는 고친
근처만 눌러 보게 되니 이런 <strong>연쇄 붕괴</strong> 잡아요.
자동 테스트는 매번 <strong> 과목 모의고사</strong> 만에 다시 보는 것과 같아서,
엉뚱한 과목의 점수 하락(=다른 기능의 고장) 바로 드러납니다.
</p>
<div className="warn">
<b>오해 금지</b> 자동 테스트가 수동 테스트를 100% 대체하는 아니에요.
"버튼 위치가 어색하다" 같은 사용성은 여전히 사람 눈이 필요합니다.
자동 테스트가 맡는 <strong>"로직이 약속대로 동작하는가"</strong>라는
지루하고 반복적인 검사 기계가 제일 잘하는 일이죠.
</div>
</Section>
<Section n={3} title="단위 테스트의 구조 — given · when · then" sub="모든 테스트는 3막짜리 연극이다">
<p>
테스트 코드는 처음 보면 낯설지만, 사실 세계 개발자들이 똑같은
<strong> 3 구조</strong> 씁니다. 상황을 차리고(<strong>given</strong>),
동작을 하나 실행하고(<strong>when</strong>), 결과를 확인한다(<strong>then</strong>).
</p>
<Code>{CODE_GWT}</Code>
<ol className="olist">
<li><strong>given</strong> 무대 준비. 테스트에 필요한 객체·데이터를 만들어요. "강의 10개짜리 코스에 김미림 학생이 있다고 치자."</li>
<li><strong>when</strong> 사건 발생. 검사하려는 <strong> 동작 하나</strong> 호출해요. 여기서 두세 가지를 한꺼번에 하면 나중에 실패했을 범인을 찾습니다.</li>
<li><strong>then</strong> 결말 확인. <span className="icode">assertThat(실제값).isEqualTo(기대값)</span> "실제로 이랬는가?" 단언(assert)해요. 기대와 다르면 테스트가 <strong>빨간불</strong> 실패합니다.</li>
</ol>
<p>
구조의 진짜 힘은 <strong>읽는 사람</strong>한테 있어요. 주석 줄만 따라 읽으면
"아, 이 코드는 이런 상황에서 이렇게 동작해야 하는구나" 바로 보이거든요.
테스트는 자체로 <strong>살아있는 사용 설명서</strong>입니다.
</p>
</Section>
<Section n={4} title="우리 코드 해부: ProgressServiceTest.java" sub="실화를 바탕으로 한 테스트">
<p>
이제 진짜 우리 플랫폼 코드를 봅시다. 여러분이 강의를 "완료" 때마다 도는
진도율 로직에는 이미 테스트가 붙어 있어요. 그중 :
</p>
<Code>{CODE_PROGRESS_TEST}</Code>
<Code>{CODE_PROGRESS_ANATOMY}</Code>
<p>
특히 ④번을 기억하세요. 좋은 테스트 케이스는 상상이 아니라 <strong>실제 사고</strong>에서
나오는 경우가 많아요. 버그를 고치면 "이 버그를 재현하는 테스트" 함께 남기는
그래야 누가 나중에 코드를 고쳐서 같은 버그가 부활해도 테스트가 즉시 잡아냅니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> IntelliJ에서 <span className="kbd">Shift</span> 연타
(Search Everywhere) <span className="icode">ProgressServiceTest</span> 입력
파일을 열어 보세요. 테스트 메서드 이름들만 위에서 아래로 읽어 보기
코드를 줄도 읽었는데 진도율 기능의 <strong>규칙 목록</strong>
머릿속에 그려지면, 그게 바로 "테스트 = 명세서" 체험입니다.
</div>
</Section>
<Section n={5} title="좋은 테스트의 조건" sub="빠르고, 독립적이어야 매일 돌린다">
<p>
테스트도 코드라서 테스트와 테스트가 있어요. 조건은 여러 가지지만,
입문 단계에서 챙길 것은 가지 <strong>빠를 </strong>, 그리고
<strong> 서로 독립적일 </strong>입니다.
</p>
<Code>{CODE_FIRST}</Code>
<p>
"빠르다" 그렇게 중요할까요? 테스트의 가치는 <strong>자주 돌리는 데서</strong> 나오거든요.
소화기가 아무리 좋아도 창고 깊숙이 있으면 쓰는 것처럼, 테스트도 실행이 귀찮으면
존재하지 않는 것과 같아요. 그래서 단위 테스트는 DB나 서버 없이
<strong> 순수한 자바 객체만으로</strong> 돌게 만드는 우선합니다
섹션 4 테스트가 <span className="icode">new</span>로만 무대를 차린 이유예요.
</p>
<p>
"독립적" <strong>시험의 공정성</strong>이라고 생각하면 돼요. 테스트는 매번
깨끗한 책상(자기만의 given)에서 시험을 봐야 합니다. 테스트가 남긴 낙서(공유 데이터)
기대는 순간, 테스트 실행 <strong>순서만 바뀌어도</strong> 멀쩡한 코드가 실패하는
미스터리가 시작돼요.
</p>
</Section>
<Section n={6} title="경계값을 노려라" sub="버그는 가운데가 아니라 가장자리에 산다">
<p>
테스트 케이스를 무한정 만들 수는 없으니 <strong> 고르는 기술</strong> 필요해요.
핵심 요령 하나: <strong>경계값</strong>(boundary value) 노리세요. 버그는 평범한
한가운데 값이 아니라, 범위가 시작되고 끝나는 <strong>아슬아슬한 가장자리</strong>에서 태어납니다.
</p>
<Code>{CODE_BOUNDARY}</Code>
<p>
가장자리일까요? 개발자가 코드를 머릿속에 떠올리는 대부분
"정상적인 경우"예요. <span className="icode">0</span>, <span className="icode">비어 있음</span>,
<span className="icode"> 최대치</span>, <span className="icode">중복</span> 같은
끝자락 상황은 상상에서 빠지기 쉽고, 빈틈이 그대로 버그가 됩니다.
놀이기구 제한이 "120cm 이상" 정확히 120.0cm인 사람을 태울지 말지
이런 걸치는 지점이 말썽이죠.
</p>
<div className="tip">
<b>실전 요령</b> 어떤 기능이든 테스트를 짜기 전에 종이에 칸을 그려 보세요:
<strong> 경우 / 1개인 경우 / (최대) 경우</strong>.
칸만 챙겨도 버그의 절반 이상을 미리 만납니다.
</div>
</Section>
<Section n={7} title="테스트가 있으면 리팩터링이 무섭지 않다" sub="안전그물 위에서만 공중제비를 돈다">
<p>
<strong>리팩터링</strong> 동작은 그대로 두고 코드의 <strong>구조만</strong> 깨끗하게
고치는 일이에요. 정리와 같죠 물건(기능) 그대로인데 배치만 바꾸는 .
문제는 "배치만 바꿨다고 생각했는데 뭔가 없어졌을" 위험입니다.
</p>
<Code>{CODE_REFACTOR}</Code>
<p>
테스트가 <strong>"동작은 그대로"임을 보증하는 감시자</strong> 되어 주면, 위험이
사라져요. 구조를 갈아엎고 테스트 실행 전부 초록불이면 "동작은 안 변했다"
증거를 손에 겁니다. 서커스 단원이 안전그물이 있을 때만 고난도 기술을 연습하듯,
개발자도 테스트가 있을 과감해질 있어요.
</p>
<p>
거꾸로 말하면, 테스트 없는 코드는 <strong>아무도 청소하려 들지 않는 </strong> 됩니다.
건드리기 무서우니까요. 그렇게 지저분함이 쌓인 코드를 개발자들은
<strong> 레거시</strong>라고 부르며 서로 떠넘기죠. 여러분이 지금 테스트를 배우는
그런 방을 만들지 않는 습관을 처음부터 들이는 거예요.
</p>
</Section>
<Section n={8} title="실습: 우리 백엔드 테스트 실행해 보기" sub="▶ 버튼 한 번, 그리고 도전 과제 QA-02">
<p>
이제 직접 돌려 봅시다. 우리 플랫폼 backend 프로젝트를 IntelliJ로 상태에서 시작하세요.
</p>
<div className="tip">
<b>직접 확인해 보기 </b> 아래 방법대로 <strong>전체 테스트를 실행</strong> 보세요.
초록 체크가 줄줄이 뜨는 순간의 안도감 그게 테스트가 매일 주는 선물입니다.
일부러 <span className="icode">Progress</span> 코드의 숫자 하나를 살짝 바꾼 다시 돌려서
<strong> 빨간불이 뜨는 </strong>까지 보고, 원상 복구하세요. (빨간불을 봐야 초록불을 믿게 돼요!)
</div>
<Code>{CODE_RUN_TESTS}</Code>
<p>
다음은 오늘 배운 전부 쓰는 <strong>도전 과제</strong>예요. 섹션 6에서 경계값 표에
"전체 강의 0개인 코스" 있었죠? 케이스가 실제 테스트 파일에는 아직 없습니다.
여러분이 채워 넣을 차례예요.
</p>
<div className="tip">
<b>직접 확인해 보기 도전 과제 (티켓 QA-02)</b> given-when-then 구조(섹션 3)
경계값 케이스(섹션 6) 하나를 <span className="icode">ProgressServiceTest.java</span>
추가하고 실행해 보세요. 아래 뼈대를 참고하되, 메서드 이름은 여러분의 문장으로!
</div>
<Code>{CODE_QA02}</Code>
<div className="warn">
<b>막히면</b> 혼자 30 이상 붙잡지 마세요. Gitea 티켓 QA-02 "여기까지 했고,
여기서 막혔다" 남기는 것도 훌륭한 진행이에요. 막힌 지점을 정확히 설명하는 능력이
테스트 작성 능력만큼 중요합니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🧪 여기까지 왔다면</h3>
<p className="muted">
테스트가 <strong>미래의 나를 지키는 보험</strong>이라는 , given-when-then이라는
<strong> 3 구조</strong>, 버그가 사는 <strong>경계값</strong>, 그리고 테스트라는
안전그물 위에서만 가능한 <strong>리팩터링</strong>까지 직접 눌러 초록불과
빨간불을 모두 봤다면 코스는 통과입니다. 도전 과제(QA-02) 끝냈다면{' '}
<Link to="/learn/git"><strong>Git 협업 기초</strong></Link> 코스에서 방금 만든
테스트를 커밋·푸시해 리뷰받는 흐름으로 이어 보세요. 백엔드 코드 자체가 궁금해졌다면{' '}
<Link to="/learn/backend"><strong>Spring Boot 백엔드 입문</strong></Link> 다음 정거장입니다.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,330 @@
// : " "
// , / GIF/
// 7 .
// : API ' ' . global.css
// (step-card, code-block, tip, warn ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_WORDS_VS_SHOT = `말로 하는 버그 리포트 vs 캡처 한 장
[말로만] [캡처 ]
"버튼 누르면 뭔가 이상해요" 에러 화면 + 빨간 화살표가
"어제는 됐는데 오늘 안 돼요" 정확히 버튼을 가리킴
"에러가 떴다 사라졌어요" F12 콘솔의 에러 메시지까지 박제
읽는 사람(멘토·팀원) 되물을 없어야 좋은 리포트예요.
"천 마디 말보다 캡처 한 장" 개발팀의 진리입니다.`;
const CODE_WIN_SHIFT_S = `Win + Shift + S — 누르는 순간 화면이 어두워지며 4가지 모드가 떠요
사각형 자유형 전체
드래그로 마우스로 클릭한 모니터
영역선택 그린모양 통째 통째로
(실무에서 90% '사각형' 모드!)
캡처하면 클립보드에 복사됨 (파일로 자동 저장 X)
· 바로 붙여넣기: Ctrl + V (PR 코멘트·채팅·문서 어디든)
· 파일로 저장/편집: 우하단 알림 클릭 캡처 도구에서 저장
친구들: PrtScn(전체 화면), Alt + PrtScn(현재 창만) 클립보드행`;
const CODE_WINDOW_TIPS = `상황별 캡처 요령 치트시트
상황 방법
하나만 깔끔하게 Alt + PrtScn (활성 창만, 배경 섞임)
또는 Win+Shift+S의 '창' 모드
길게 스크롤되는 페이지 F12 Ctrl+Shift+P "screenshot" 입력
"Capture full size screenshot" 선택
(브라우저가 페이지 전체를 장으로 저장!)
드롭다운·툴팁이 열린 순간 Win+Shift+S는 누르는 순간 메뉴가 닫힘
PrtScn으로 전체를 찍고 나중에 잘라내기
캡처를 5 뒤에 캡처 도구(Snipping Tool) 지연 타이머`;
const CODE_RECORD_TOOLS = `화면 녹화 도구 3종 비교 — "가끔만 일어나는 버그"의 결정적 증거
도구 결과물 특징
Win + G (게임 ) MP4 영상 윈도우 내장, 설치 불필요.
Win+Alt+R로 녹화 시작/정지
ScreenToGif GIF 가볍고 무료. 프레임 단위 편집 가능,
채팅·PR에 바로 재생돼서 개발자 최애
캡처 도구(Win11) MP4 영상 캡처 도구 앱의 '녹화'
영역 지정 녹화 지원
언제 쓰나?
· 3~10초짜리 "누르면 이렇게 돼요" GIF (받는 사람이 클릭 없이 )
· 30 넘는 재현 절차, 소리 필요 MP4`;
const CODE_ANNOTATE = `주석의 3종 세트 — 이것만 있으면 충분해요
화살표 "여기!" 시선을 점으로 모음
박스 문제 영역을 테두리로 강조
텍스트 Aa "기대: 저장됨 / 실제: 500 에러" 설명
어디서 다나?
· 그림판: 캡처 붙여넣기(Ctrl+V) 도형·텍스트 도구 (설치 불필요)
· 캡처 도구: 캡처 직후 알림 클릭 ·형광펜
· ScreenToGif: 프레임 위에 직접 그리기
색은 빨강 하나로 통일하세요 주석이 알록달록하면
어디를 보라는 건지 오히려 헷갈립니다.`;
const CODE_MASK_CHECKLIST = `공유 전 3초 점검 체크리스트 — 캡처에 이런 게 찍혀 있진 않나요?
주소창·설정 화면의 토큰, API , 비밀번호
F12 Network 탭의 Authorization 헤더, 쿠키
이메일 주소, 전화번호, 실명 ( 것도, 남의 것도)
.env 파일, DB 접속 문자열이 열려 있는 에디터
배경의 다른 채팅, 메일 미리보기
가리는 : 그림판에서 '완전 불투명한' 단색 박스로 덮기.
모자이크·흐림은 복원되는 사례가 있어요 덮을 확실하게!`;
const CODE_ASSIGNMENT = `과제: 우리 학습 플랫폼 버그 리포트 캡처 1장 만들기
대상 찾기 우리 플랫폼에서 "고치고 싶은 것" 하나를 고르세요.
진짜 버그가 없다면 개선하고 싶은 UI(정렬이 어긋난 버튼,
좁은 여백, 어색한 문구) OK!
Win+Shift+S 사각형 모드로 문제 부분이 보이게 캡처
그림판에서 주석 3 세트:
· 빨간 박스로 문제 영역 표시
· 화살표로 정확한 지점 지시
· 텍스트로 "기대한 동작 / 실제 동작" 줄씩
3 점검(섹션 6 체크리스트) PNG로 저장
Gitea(edu.awesomedevapp.com:3000) 과제 저장소 이슈에
이미지 첨부해서 제출 이슈 본문에 재현 절차도 번호로!
멘토는 "캡처만 보고 되묻지 않고 이해되는가" 기준으로 봅니다.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '왜 캡처인가' },
{ n: 2, label: 'Win+Shift+S' },
{ n: 3, label: '창·스크롤 캡처' },
{ n: 4, label: 'GIF·화면 녹화' },
{ n: 5, label: '주석 달기' },
{ n: 6, label: '개인정보 가리기' },
{ n: 7, label: '실습 과제' },
];
export default function ToolCapturePage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 부록 · 도구</div>
<h1>캡처와 화면 녹화<br /> 마디 말보다 스크린샷 </h1>
<p>
개발자의 하루는 "이거 보세요" 연속이에요 PR 리뷰, 버그 리포트, 질문 채팅
전부 캡처 장이 있고 없고에 따라 전달력이 하늘과 차이입니다.
코스에서는 윈도우 기본 도구만으로 <strong>찍고, 녹화하고, 주석 달고,
안전하게 공유하는</strong> 과정을 손에 익혀요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 40</span>
<span className="chip">실습 2 + 제출 과제 1</span>
<span className="chip">준비물: 윈도우 PC 하나</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="개발자의 소통은 스크린샷으로" sub="PR·버그 리포트의 공용어">
<p>
여러분이 앞으로 어썸데브에서 코드를 올리면(<strong>PR</strong>), 리뷰하는 사람이
가장 먼저 궁금해하는 "그래서 화면이 어떻게 바뀌었는데?"예요. 말로 쓰는
것보다 <strong>변경 / 캡처 </strong> 붙이는 쪽이 빠르게 전달됩니다.
버그 리포트도 마찬가지 에러는 목격자의 증언보다 <strong>현장 사진</strong>
제일 확실한 증거거든요.
</p>
<Code>{CODE_WORDS_VS_SHOT}</Code>
<p>
캡처는 "잘하면 좋은 기술" 아니라 <strong>협업의 기본 예의</strong> 가까워요.
받는 사람이 화면을 상상하게 만들지 말고, 그냥 보여 주세요. 코스의 목표는
단순합니다 <strong>생각과 동시에 손이 캡처 단축키를 누르는 상태</strong> 되는 .
</p>
<div className="tip">
<b>미리 보기</b> Gitea(<span className="icode">edu.awesomedevapp.com:3000</span>)
아무 저장소나 열어 이슈·PR에 이미지가 첨부된 구경해 보세요. 리포트가
어떻게 생겼는지 눈에 담아 두면, 섹션 7 과제가 훨씬 쉬워져요.
</div>
</Section>
<Section n={2} title="윈도우 기본 캡처 마스터" sub="Win+Shift+S — 손이 외울 때까지">
<p>
별도 프로그램 설치 없이, 윈도우에 이미 최고의 캡처 도구가 들어 있어요.
<span className="kbd">Win</span> + <span className="kbd">Shift</span> +{' '}
<span className="kbd">S</span> 키가 여러분의 카메라 셔터입니다.
</p>
<Code>{CODE_WIN_SHIFT_S}</Code>
<p>
핵심은 <strong>캡처 결과가 클립보드로 간다</strong> 점이에요. 복사(Ctrl+C)
텍스트처럼, 캡처한 이미지도 <span className="kbd">Ctrl</span> +{' '}
<span className="kbd">V</span> 채팅창·이슈·문서에 바로 붙습니다.
"찍기 → 붙이기"까지 3 속도가 캡처를 습관으로 만들어 줘요.
</p>
<div className="tip">
<b>직접 확인해 보기 </b> 지금 바로 <span className="kbd">Win+Shift+S</span> 눌러
코스의 히어로 영역( 제목 부분) 사각형 모드로 캡처하고, 그림판을 열어{' '}
<span className="kbd">Ctrl+V</span> 붙여 보세요. 10 안에 되면 합격!
이어서 <span className="kbd">Alt+PrtScn</span> 눌러 보고 결과가 어떻게 다른지 비교해 보세요.
</div>
</Section>
<Section n={3} title="특정 창·스크롤 캡처 요령" sub="사각형 드래그만으로는 부족한 순간들">
<p>
기본 사각형 캡처로 90% 해결되지만, 실무에선 까다로운 상황이 나와요.
"브라우저 창만 깔끔하게", "화면보다 긴 페이지를 통째로", "마우스를 올려야만
보이는 메뉴를" 이럴 쓰는 요령들입니다.
</p>
<Code>{CODE_WINDOW_TIPS}</Code>
<p>
특히 <strong>스크롤 전체 캡처</strong> 프론트엔드 작업에서 자주 필요해요.
우리 React 페이지처럼 세로로 화면을 리뷰받을 , 조각 캡처 다섯 장보다
전체 장이 훨씬 보기 좋거든요. 브라우저 개발자 도구(<span className="kbd">F12</span>)
숨어 있는 기능이라 아는 사람만 씁니다 이제 여러분도 아는 사람이에요.
</p>
<div className="tip">
<b>직접 확인해 보기 </b> 페이지에서 <span className="kbd">F12</span> {' '}
<span className="kbd">Ctrl+Shift+P</span> <span className="icode">screenshot</span>이라고
입력 <strong>Capture full size screenshot</strong> 선택해 보세요.
지금 보고 있는 코스 페이지 전체가 PNG 장으로 다운로드됩니다.
히어로부터 마무리 카드까지 들어갔는지 열어서 확인!
</div>
</Section>
<Section n={4} title="GIF와 화면 녹화" sub="'가끔만 일어나는 버그'의 결정적 증거">
<p>
정지 화면으로는 설명이 되는 버그가 있어요. "버튼을 <strong>빠르게 </strong>{' '}
누르면 목록이 사라져요" 같은 것들 사진으로는 범행 순간을 잡죠.
이럴 <strong>동영상(또는 GIF)</strong> CCTV 역할을 합니다.
재현 절차를 글로 쓰는 대신, 재현하는 손을 그대로 녹화해서 보여 주는 거예요.
</p>
<Code>{CODE_RECORD_TOOLS}</Code>
<p>
개발자들이 GIF를 유독 좋아하는 이유가 있어요. 채팅이나 Gitea 이슈에 올리면{' '}
<strong>클릭 없이 자동 재생</strong>되거든요. 받는 사람이 플레이어를 필요도,
소리를 필요도 없이 스크롤하다가 바로 봅니다. 짧은 버그 재현엔 GIF,
시연이나 설명이 필요하면 MP4 기준 하나만 기억하세요.
</p>
<div className="tip">
<b>녹화 10 준비</b> 브라우저 탭을 정리하고, 재현 절차를 머릿속으로
리허설한 녹화 버튼을 누르세요. 30초짜리 깔끔한 영상이
헤매는 3분짜리 영상보다 훨씬 좋은 리포트입니다.
</div>
</Section>
<Section n={5} title="캡처에 주석 달기" sub="화살표와 박스는 캡처의 자막이다">
<p>
캡처를 받아 사람의 반응은 의외로 이거예요 <strong>"그래서 어딜 보라는
거죠?"</strong> 화면엔 버튼이 스무 개인데 문제는 하나거든요. 주석은 캡처에
자막을 다는 일입니다. 시선을 문제 지점으로 데려다주는 내비게이션이에요.
</p>
<Code>{CODE_ANNOTATE}</Code>
<p>주석 달기의 순서는 같아요.</p>
<ol className="olist">
<li>캡처를 그림판에 붙여넣는다 (<span className="kbd">Ctrl+V</span>)</li>
<li>문제 영역에 <strong>빨간 박스</strong> 두른다</li>
<li>헷갈릴 여지가 있으면 <strong>화살표</strong> 정확한 지점을 찍는다</li>
<li>여백에 <strong>"기대: ○○ / 실제: ××"</strong> 줄을 적는다</li>
<li>PNG로 저장한다 (JPG는 글자가 뭉개질 있어요)</li>
</ol>
<p>
가지가 들어간 캡처는 자체로 완결된 버그 리포트예요. 받는 사람이
"어디서요? 원래는 어떻게 돼야 하는데요?"라고 되물을 일이 없으니까요.
</p>
</Section>
<Section n={6} title="개인정보 가리기 습관" sub="공유 버튼 누르기 전, 3초만 멈추기">
<div className="warn">
<b>실제로 일어나는 사고</b> 개발자가 무심코 올린 캡처 장에{' '}
<strong>API 토큰</strong> 찍혀 있어 서비스 전체 키를 재발급한 사례,
F12 Network 캡처에 <strong>로그인 쿠키</strong> 노출돼 계정이 도용된 사례,
배경 채팅창에 고객 <strong>이메일 목록</strong> 보여 개인정보 사고로 번진 사례
전부 "설마 이런 것까지 찍혔겠어?" 하는 방심에서 시작됐어요.
캡처는 화면의 <strong>모든 </strong> 담습니다. 내가 보라고 곳만 담는 아니라요.
</div>
<Code>{CODE_MASK_CHECKLIST}</Code>
<p>
특히 우리처럼 <strong>Gitea 이슈</strong> 캡처를 올리는 환경에서는, 올라간
이미지가 팀원 전체에게 보이고 기록으로 남아요. 습관을 만드는 답입니다
<strong> 붙여넣기 전에 무조건 3 훑어보기.</strong> 셔터를 누르는 손보다
점검하는 눈이 박자 늦게 따라오면 사고는 납니다.
</p>
<div className="tip">
<b>가릴 확실하게</b> 흐림(블러)·모자이크 처리는 도구로 복원된 사례가 있어요.
토큰이나 비밀번호는 그림판의 <strong>완전 불투명한 단색 박스</strong> 덮으세요.
제일 안전한 애초에 민감한 창을 닫고 다시 찍는 !
</div>
</Section>
<Section n={7} title="실습 — 버그 리포트 캡처 제출" sub="이 코스의 기술을 전부 한 장에 담기">
<p>
이제 배운 전부 꺼낼 시간이에요. 우리 학습 플랫폼(지금 보고 있는 서비스!)
돌아다니면서 <strong>버그 리포트용 캡처 1</strong> 완성해 과제로 제출합니다.
섹션 1 "되묻지 않아도 되는 리포트" 목표예요.
</p>
<Code>{CODE_ASSIGNMENT}</Code>
<p>
"버그를 못 찾으면 어떡하죠?"라는 걱정은 접어 두세요 과제의 채점 기준은
버그의 심각도가 아니라 <strong>전달력</strong>이에요. 사소한 여백 문제라도
박스·화살표·기대/실제 줄이 제대로 들어가 있으면 만점입니다.
반대로 진짜 버그를 찾았어도 어딜 보라는 건지 모르겠는 캡처라면 다시!
</p>
<div className="tip">
<b> 단계 </b> 여유가 있다면 같은 문제를 <strong>ScreenToGif로 3~5 GIF</strong>로도
만들어 이슈에 함께 첨부해 보세요. 정지 화면 + 움직이는 재현 영상 조합은
실무 버그 리포트의 정석입니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>📸 여기까지 왔다면</h3>
<p className="muted">
이제 <strong>찍고(Win+Shift+S) 담고(스크롤· 캡처) 움직이고(GIF·녹화)
가리키고(주석) 지키는(개인정보 점검)</strong> 다섯 동작이 손에 들어왔어요.
기술은 앞으로 모든 코스의 과제 제출에서 계속 쓰입니다.
다음은 캡처를 첨부할 무대 <Link to="/learn/git"><strong>Git과 Gitea</strong></Link> 코스에서
이슈·PR에 리포트를 올리는 협업 흐름을 이어서 배워 보세요.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,397 @@
// : " " F12
// 8 .
// ( Elements Console Network Lighthouse Sources )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_TAB_TOUR = `F12를 누르면 열리는 탭들 — 자동차 정비소에 비유하면:
하는 정비소로 치면
Elements 화면의 HTML/CSS를 실시간으로 보닛 열고 엔진 구경
Console JS 실행·에러 메시지 확인 계기판 경고등 판독기
Network 서버와 주고받은 요청 전부 기록 블랙박스 영상
Sources 실제 JS 코드 + 브레이크포인트 정비 매뉴얼 + 일시정지 버튼
Lighthouse 성능·접근성 자동 채점 정기 검사소
반응형 모드 모바일 화면 시뮬레이션 시승 코스 (좁은 골목 버전)
전부 외울 필요 없어요. 코스에서 하나씩 직접 열어 거예요.`;
const CODE_OPEN_WAYS = `개발자도구 여는 법 3가지 (Windows 기준):
1) F12 제일 빠름, 이것만 외워도 OK
2) Ctrl + Shift + I F12와 같음
3) 화면 아무 데나 우클릭 "검사" 클릭한 요소로 바로 점프!
닫는 것도 F12. 열림/닫힘 토글이에요.
도킹 위치 바꾸기: 개발자도구 우상단 메뉴
아래·오른쪽·별도 선택. 좁은 노트북이면 별도 추천!`;
const CODE_ELEMENTS_EDIT = `Elements 탭에서 할 수 있는 실시간 수정:
텍스트 바꾸기
HTML 트리에서 글자를 더블클릭 바로 타이핑 Enter
CSS 바꾸기
요소 선택 오른쪽 Styles 패널에서 클릭
color: red, font-size: 40px ... 마음대로!
요소 지우기
요소 선택 Delete 화면에서 사라짐
중요한 사실: 모든 수정은 "내 브라우저의 복사본"에만 적용돼요.
서버의 진짜 파일은 1 바뀝니다.
새로고침(F5) 번이면 전부 원상복구!`;
const CODE_CONSOLE_BASIC = `Console 탭 = 브라우저 안에서 바로 실행되는 JS 계산기
> 1 + 1
2
> "어썸" + "데브"
'어썸데브'
> document.title
'AWESOMEDEV 학습 플랫폼' 지금 보는 페이지의 제목!
> document.querySelectorAll('a').length
37 페이지의 링크 개수
우리가 코드에 심어 console.log(...) 출력도 전부 여기로 나와요.
React 개발할 "값이 지금 뭐지?" 싶으면 제일 먼저 여는 탭입니다.`;
const CODE_ERROR_READ = `에러 메시지 읽는 법 — 빨간 글씨는 무섭지 않아요, 친절한 제보예요.
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
at UserCard (UserCard.jsx:12)
에러 종류 무슨 일이 있었나 어디서 (파일:줄번호!)
(TypeError) "undefined에서 name을 읽으려 했다" UserCard.jsx 12번째
해석: "user가 아직 없는데(undefined) user.name을 꺼내려 했구나"
오른쪽 파일명:줄번호를 클릭하면 코드로 바로 이동합니다.
에러는 범인이 아니라 목격자 진술이에요. 끝까지 읽는 사람이 이깁니다.`;
const CODE_NETWORK_ANATOMY = `Network 탭에서 요청 하나를 클릭하면 보이는 것들:
요청 목록
Name Status Type Size Time
posts 200 fetch 2.1 kB 48 ms
클릭하면 열리는 상세 패널:
Headers 어디로(URL)·어떻게(GET/POST)·무슨 신분증(토큰) 들고 갔나
Payload POST일 우리가 서버에 보낸 데이터 (요청 본문)
Response 서버가 돌려준 원본 답장 React가 받는 진짜 JSON!
Timing 어느 구간에서 시간이 걸렸나 (기다림? 다운로드?)
Status 빠른 판독법:
2xx 성공 / 3xx 다른 곳으로 안내 / 4xx (요청) 잘못 / 5xx 서버 잘못
401 "로그인 안 됨", 404 "그런 주소 없음" 단골손님이니 얼굴을 익혀 두세요.`;
const CODE_RESPONSIVE = `반응형 모드 (Device Toolbar) 켜는 법:
개발자도구 열린 상태에서 Ctrl + Shift + M
(또는 좌상단의 📱 모양 아이콘 클릭)
상단 바에서 있는 :
- 기기 선택: iPhone, Galaxy ... 프리셋 고르기
- 치수 직접 입력: 375 x 812 처럼 픽셀 지정
- 배율(%) 조절, 회전(가로/세로 전환)
주의: 화면 "크기" 흉내 내는 거예요.
터치 감도, 실제 성능, 사파리 전용 버그까지는 잡아요.
그래도 "모바일에서 버튼이 밀려나요" 같은 레이아웃 문제의
90% 여기서 미리 발견할 있습니다.`;
const CODE_LIGHTHOUSE = `Lighthouse 돌리는 법:
1) 개발자도구 Lighthouse ( 보이면 » 눌러 더보기)
2) Mode: Navigation / Device: Desktop 선택
3) "Analyze page load" 클릭 30초쯤 기다림
나오는 점수 4가지 (100 만점):
Performance 빠른가? (로딩 속도)
Accessibility 모두가 있나? (스크린리더, 대비...)
Best Practices 기본기를 지켰나? (https, 콘솔 에러...)
SEO 검색엔진이 읽을 있나?
점수 아래 "Opportunities" 개선 방법까지 적어 줘요.
"이미지가 너무 커요, 이만큼 줄이면 몇 초 빨라져요" 같은 식으로요.
무료 컨설턴트가 브라우저에 내장돼 있는 !`;
const CODE_BREAKPOINT = `Sources 탭 브레이크포인트 — 코드에 "일시정지 버튼" 달기
1) Sources 왼쪽 파일 트리에서 JS 파일 열기
2) 멈추고 싶은 줄의 "줄번호" 클릭 파란 표시 = 브레이크포인트
3) 코드가 실행되는 순간 화면이 얼어붙고,
시점의 모든 변수 값이 오른쪽 패널에 나타남!
멈춘 조작 버튼:
(F8) 다음 브레이크포인트까지 재생
(F10) 줄씩 실행 (다음 줄로)
console.log를 10 심는 대신 브레이크포인트 1개로
"그 순간" 통째로 열어 보는 디버깅의 정석입니다.
(영화를 일시정지하고 배경 소품까지 뜯어보는 느낌!)`;
const CODE_MISSION = `종합 미션: 우리 플랫폼에서 탭당 5분씩, 25분 투어
준비: 우리 학습 플랫폼을 연다 F12
미션 1 · Elements (5)
페이지 제목을 우클릭 검사 더블클릭해서
이름이 들어간 제목으로 바꿔 보기 F5로 원상복구 확인
미션 2 · Console (5)
document.title 출력해 보기
document.querySelectorAll('a').length 링크 개수 세기
미션 3 · Network (5)
Network 열고 F5 요청 하나 클릭
Status 코드, Response의 JSON 내용, 걸린 시간(ms) 3가지 찾기
(로그인해서 나오는 요청이면 Headers에서 Authorization도 구경!)
미션 4 · 반응형 모드 (5)
Ctrl+Shift+M iPhone 프리셋
우리 플랫폼에서 레이아웃이 깨지거나 어색한 1군데 찾아 메모
미션 5 · Lighthouse (5)
페이지에서 Analyze 실행
4 점수 기록 + Opportunities에서 개선 제안 1 읽어 보기
미션 4에서 찾은 "어색한 곳" 미션 5 점수를
멘토에게 공유하면 코스 수료!`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: 'F12의 세계 투어' },
{ n: 2, label: 'Elements' },
{ n: 3, label: 'Console' },
{ n: 4, label: 'Network' },
{ n: 5, label: '반응형 모드' },
{ n: 6, label: 'Lighthouse' },
{ n: 7, label: 'Sources 맛보기' },
{ n: 8, label: '종합 실습 미션' },
];
export default function ToolDevtoolsPage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 부록 · 도구</div>
<h1>크롬 개발자도구<br /> F12 하나로 열리는 브라우저 정비소</h1>
<p>
여러분이 매일 쓰는 크롬 안에는 프로 개발자들이 매일 쓰는 정비소가 통째로
숨어 있어요. 코스에서는 <span className="kbd">F12</span> 번으로 문을 열고,
하나하나를 <strong>우리 플랫폼 위에서 직접 눌러 보며</strong> 익힙니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">실습 미션 5</span>
<span className="chip">준비물: 크롬 + F12 </span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="F12의 세계 — 전체 투어" sub="브라우저 속 정비소의 평면도부터">
<p>
웹페이지는 <strong>완성된 화면</strong> 보여 주지만, 개발자도구를 열면 화면이
<strong> 어떻게 만들어졌고, 지금 무슨 일이 벌어지는지</strong> 전부 보여요.
자동차로 치면 겉모습만 보다가 처음으로 보닛을 열어 보는 순간입니다.
</p>
<Code>{CODE_TAB_TOUR}</Code>
<p>
여는 방법부터 손에 익혀 둡시다. 특히 3(우클릭 검사) "지금 눈에 보이는
<strong> 바로 버튼</strong> 코드가 궁금할 " 요소로 직행하는 지름길이에요.
</p>
<Code>{CODE_OPEN_WAYS}</Code>
<div className="tip">
<b>겁먹지 않아도 되는 이유</b> 개발자도구에서 누르든 <strong>서버의 진짜 파일은
절대 바뀌지 않아요</strong>. 전부 브라우저 안에서만 벌어지는 일이라, 최악의 경우에도
<span className="kbd">F5</span> 번이면 리셋됩니다. 마음껏 눌러 보세요.
</div>
</Section>
<Section n={2} title="Elements — 화면을 실시간으로 뜯어고치기" sub="HTML·CSS 수정, 단 새로고침하면 원상복구">
<p>
Elements 탭은 지금 화면의 <strong>HTML 구조</strong>(왼쪽 트리) 요소에 적용된
<strong> CSS</strong>(오른쪽 Styles 패널) 보여 줘요. 그런데 보기만 하는 아니라
<strong> 자리에서 바로 고칠 있습니다</strong> 마치 점토로 만든 모형을
손가락으로 꾹꾹 눌러 보는 것처럼요.
</p>
<Code>{CODE_ELEMENTS_EDIT}</Code>
<p>
그래서 뉴스 사이트 헤드라인을 "우리 반 급식 최고" 바꾸는 장난도 가능해요.
스크린샷을 찍으면 진짜처럼 보이지만, <strong>새로고침하는 순간 마법이 풀립니다</strong>.
거꾸로 말하면 인터넷에 떠도는 "잔고 100억 캡처" 같은 것도 이렇게 10초면
만들 있다는 뜻이에요. 캡처 화면을 믿게 되는 것도 탭이 주는 교훈입니다.
</p>
<div className="warn">
<b>장난은 화면까지만</b> 조작한 화면을 캡처해서 진짜인 것처럼 남에게 보여 주는
장난이 아니라 <strong>속임수</strong> 있어요. 연습은 자유, 유통은 금지!
</div>
<div className="tip">
<b>직접 확인해 보기</b> 지금 페이지의 제목 "크롬 개발자도구" 우클릭
<strong> 검사</strong> 해당 텍스트를 더블클릭해서 여러분 이름으로 바꿔 보세요.
실무에서는 방법으로 <strong>CSS 값을 이리저리 바꿔 보고, 마음에 값만
진짜 코드에 옮겨 적는</strong> 식으로 디자인을 실험합니다.
</div>
</Section>
<Section n={3} title="Console — JS 실행기 겸 에러 판독기" sub="빨간 글씨와 친해지는 시간">
<p>
Console은 가지 얼굴을 가졌어요. 하나는 <strong>JS를 즉석에서 실행하는 계산기</strong>,
다른 하나는 페이지에서 터진 <strong>에러가 모여드는 신문고</strong>입니다.
</p>
<Code>{CODE_CONSOLE_BASIC}</Code>
<p>
그리고 진짜 중요한 <strong>에러 읽기</strong>. "페이지가 하얗게 나와요",
"버튼이 안 눌려요" 같은 문제의 답은 십중팔구 Console의 빨간 글씨 안에 이미 적혀 있어요.
문제는 대부분 무서워서 읽는다는 것뿐이죠.
</p>
<Code>{CODE_ERROR_READ}</Code>
<div className="tip">
<b>수습 개발자의 황금 습관</b> "안 돼요"라고 질문하기 전에 Console을 먼저 열어서
<strong> 빨간 글씨를 그대로 복사</strong> 두세요. 멘토에게 "안 돼요" 대신
"이 에러가 나요"라고 물으면 답이 10 빨리 옵니다.
</div>
</Section>
<Section n={4} title="Network — 서버와의 대화 도청하기" sub="우리 React가 Spring Boot와 주고받는 말, 전부 기록됨">
<p>
우리 플랫폼은 <strong>React</strong>(화면) <strong>Spring Boot</strong>(서버)
"데이터 주세요" 하고 요청을 보내는 구조예요. Network 탭은 <strong>모든 요청과
응답을 기록하는 블랙박스</strong>입니다. "데이터가 안 나와요" 화면(React) 문제인지
서버(Spring Boot) 문제인지, 탭을 보면 갈림길이 정확히 보여요.
</p>
<Code>{CODE_NETWORK_ANATOMY}</Code>
<p>
판별법은 간단합니다. <strong>Response에 데이터가 멀쩡히 있는데 화면에 나온다</strong>?
React 문제. <strong>Status가 4xx/5xx다</strong>? 요청이나 서버 문제.
가지 분기만 익혀도 디버깅 시간이 절반으로 줄어요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 우리 플랫폼에서 Network 탭을 채로
<span className="kbd">F5</span> 필터에서 <strong>Fetch/XHR</strong> 남기면
API 요청만 보여요. 하나 클릭해서 <strong>Response</strong> 탭의 JSON을 열어 보세요
여러분이 화면에서 보던 목록이 <strong>원재료(JSON) 상태</strong> 그대로 들어 있습니다.
"네트워크의 이해" 코스에서 배운 IP:포트도 Headers에서 찾아보세요!
</div>
</Section>
<Section n={5} title="반응형 모드 — 내 PC에서 모바일 점검" sub="Ctrl+Shift+M, 폰 없이 폰 화면 보기">
<p>
우리 플랫폼을 쓰는 사람 절반은 폰으로 접속해요. 그런데 개발은 넓은 모니터로 하죠.
그래서 "내 화면에선 예뻤는데 폰에선 버튼이 반쯤 잘려요" 사고가 납니다.
반응형 모드는 <strong>브라우저 안에 가짜 화면을 띄워서</strong> 이런 사고를
배포 전에 잡아 주는 시승 코스예요.
</p>
<Code>{CODE_RESPONSIVE}</Code>
<div className="tip">
<b>직접 확인해 보기</b> <span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+
<span className="kbd">M</span> 누르고 iPhone 프리셋을 골라 우리 플랫폼을 돌아다녀 보세요.
글자가 삐져나오거나, 버튼이 너무 작거나, 표가 잘리는 곳이 있나요?
<strong> 찾으면 그게 바로 여러분의 QA 리포트</strong>입니다.
</div>
</Section>
<Section n={6} title="Lighthouse — 성능 점수 한 입" sub="우리 사이트, 몇 점일까?">
<p>
Lighthouse(등대) 구글이 크롬에 넣어 <strong>자동 검사관</strong>이에요.
버튼 번이면 페이지를 구석구석 훑고 <strong>성능·접근성·모범사례·SEO</strong>
과목의 성적표를 줍니다. 건강검진처럼 "지금 어디가 안 좋은지 + 어떻게 고치는지"까지
알려 주는 포인트예요.
</p>
<Code>{CODE_LIGHTHOUSE}</Code>
<div className="warn">
<b>점수에 목숨 걸지 않기</b> 같은 페이지도 PC 상태·네트워크에 따라 점수가 점씩
출렁여요. 100 만들기 게임이 아니라, <strong>"뭘 고치면 좋아지는지" 목록을 얻는
도구</strong> 쓰는 정답입니다. 특히 개발 서버(npm run dev) 최적화 전이라
점수가 낮게 나오는 정상이에요.
</div>
</Section>
<Section n={7} title="Sources — 브레이크포인트 맛보기" sub="코드에 일시정지 버튼 달기">
<p>
Console에 <span className="icode">console.log</span> 도배하며 값을 추적하는 것도
방법이지만, 프로들은 단계 위의 무기를 써요 <strong>브레이크포인트</strong>.
코드가 실행되다가 내가 찍은 줄에서 <strong>시간이 멈추고</strong>, 순간의 변수들을
전부 들여다볼 있습니다.
</p>
<Code>{CODE_BREAKPOINT}</Code>
<p>
오늘은 "이런 게 있다"까지만 알면 충분해요. 나중에 React 코스에서 상태(state)
꼬이는 버그를 만나면, 그때 섹션으로 돌아와서 진짜 무기로 꺼내 쓰게 거예요.
</p>
<div className="tip">
<b>가벼운 체험</b> Sources 탭을 열고 <span className="kbd">Ctrl</span>+<span className="kbd">P</span>
눌러 보세요 파일 이름으로 검색해서 여는 창이 떠요. 우리 플랫폼의 JS 파일 하나를
열어서 <strong>"내가 매일 보는 화면의 실제 코드"</strong> 구경만 봐도 오늘은 성공!
</div>
</Section>
<Section n={8} title="종합 실습 — 탭당 5분 투어 미션" sub="배운 다섯 탭, 우리 플랫폼 위에서 전부 눌러 보기">
<p>
도구는 <strong>손에 익어야 도구</strong>예요. 아래 미션을 체크리스트 삼아
우리 플랫폼 위에서 처음부터 끝까지 돌아 보세요. 25분이면 됩니다.
</p>
<Code>{CODE_MISSION}</Code>
<div className="tip">
<b>공부 </b> 미션을 하다 "어?" 싶은 순간(이상한 에러, 느린 요청, 깨진 레이아웃)
있으면 <strong>스크린샷 + 어느 탭에서 봤는지</strong> 메모해 두세요.
그게 쌓이면 여러분만의 디버깅 노트가 됩니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🔧 여기까지 왔다면</h3>
<p className="muted">
이제 <span className="kbd">F12</span> 여러분에게 "무서운 검은 창" 아니라
<strong> 매일 여는 공구함</strong>이에요. 특히 Network 탭에서 요청과 응답의 정체가
궁금하다면 <Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스에서
데이터가 달려온 전체를 배울 있어요. 그리고 개발자도구는 앞으로 모든 코스의
<strong> 실습 동반자</strong>입니다 어떤 페이지를 만들든 F12를 열어 두는 습관,
오늘부터 시작!
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,395 @@
// : "Gitea 200% " Git (edu.awesomedevapp.com:3000)
// ' ' 8 .
// ( Pull Request diff · )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_GITEA_MAP = `우리 Git 서버 한눈에 보기 — edu.awesomedevapp.com:3000
[브라우저] HTTPS> [Caddy] > [Gitea 컨테이너(Docker)] > [AWS EC2 서버]
Gitea 화면(대시보드) 구성:
상단바: 로고 · 검색창 · [+] 새로 만들기 · 프로필 사진
왼쪽 가운데
저장소 목록 활동 피드 동료들이 방금
(Repository) 푸시·이슈·PR 기록이 실시간으로
흘러가는 '회사 타임라인'
포트 3000 = "edu.awesomedevapp.com이라는 건물의 3000호에
Gitea가 산다" 뜻이에요. (네트워크 코스의 IP·포트 그대로!)`;
const CODE_REPO_SCREEN = `저장소 화면의 3대 정보 — 이것만 읽어도 프로젝트가 보인다
파일 탐색기 (가운데 영역)
폴더/파일 목록 + 오른쪽에 "마지막 커밋 메시지 · 시간"
파일을 누가 마지막으로 건드렸는지 요약
커밋 히스토리 (Commits 숫자 클릭)
프로젝트의 일기장. 최신이 .
fcc4337 feat: 회원가입 화면 추가 김멘토 2시간
72ddc36 fix: 로그인 버튼 겹침 수정 어제
커밋 하나 = 일기 . 클릭하면 그날 바뀐 내용 전부(diff)
브랜치 선택 (파일 목록 드롭다운, 보통 main)
브랜치 = 평행우주. main은 '정본 우주',
feature/signup은 '회원가입 실험 중인 우주'.
드롭다운을 바꾸면 우주의 파일들이 보여요.`;
const CODE_ISSUE_ANATOMY = `이슈(Issue) 하나의 구조 — 할 일 티켓의 해부도
#42 로그인 버튼이 모바일에서 잘려 보임 [열림 Open]
본문: 어떤 문제인지 / 재현 방법 / 스크린샷
아래로 댓글이 대화처럼 이어짐 (카톡 스레드 느낌)
오른쪽 사이드바:
담당자(Assignee) 일을 '누가' 하는가
라벨(Label) bug / 기능요청 / 질문 ... 색깔 태그
마일스톤 어떤 목표(버전) 속하는가
상태는 가지: Open(진행 ) Closed(!)
커밋 메시지에 "fix: 로그인 버튼 수정 (#42)"처럼 번호를 적으면
커밋과 이슈가 자동으로 연결됩니다.`;
const CODE_PR_FLOW = `Pull Request(PR) = "제 브랜치 작업, 정본(main)에 합쳐 주세요" 요청서
브랜치에서 작업 푸시 PR 열기 리뷰 승인 병합(Merge)
feature/signup
PR: "회원가입 화면 추가했어요, 봐 주세요"
main > main (병합 하나로!)
PR 화면의 3형제:
Conversation 대화 설명, 댓글, 리뷰 의견이 모이는
Commits PR에 포함된 커밋 목록
Files Changed 바뀐 파일 전부의 diff 리뷰어가 사는
리뷰어가 있는 :
· 특정 줄에 코멘트 달기 ( 번호에 마우스 [+] 클릭)
· Approve(승인) / Request changes(수정 요청)
숙제를 내기 전에 짝꿍이 검사해 주는 그게 코드 리뷰예요.`;
const CODE_DIFF_READ = `diff 읽는 법 — 초록은 '새로 온 줄', 빨강은 '떠난 줄'
@@ -12,4 +12,5 @@ "12번째 줄 근처가 바뀌었다" 좌표
function LoginButton() { 회색: 바뀐 (문맥용)
- return <button>로그인</button>; 빨강(-): 지워진
+ return (
+ <button className="btn-primary">로그인</button>
+ ); 초록(+): 새로 생긴
}
읽는 요령:
1) 빨강·초록을 위아래로 짝지어 보세요 "뭐가 뭐로 바뀌었나"
2) 줄만 바뀌어도 빨강 1 + 초록 1줄로 표시돼요 (수정 = 삭제+추가)
3) 파일 단위 요약: "3 files changed, +42 7"
파일 3개에서 42 생기고 7 사라짐. 리뷰 분량 가늠자!`;
const CODE_PROFILE = `프로필 페이지 = 나의 개발자 명함
edu.awesomedevapp.com:3000/내아이디 접속하면:
[아바타] 이름 · 소속
잔디밭(Heatmap): 날짜별 활동량이 초록 칸으로 채워짐
커밋·이슈·PR을 만들 때마다 칸이 진해져요.
수습 기간이 끝날 때쯤 잔디밭이 여러분의 성장 그래프!
꾸미는 : 오른쪽 프로필 사진 Settings(설정)
· Avatar 사진 업로드 (얼굴이든 캐릭터든 OK)
· Full Name 한글 이름 동료가 나를 찾기 쉽게!
· Biography 소개 (: "26기 수습 · 프론트엔드 배우는 중")`;
const CODE_WIKI_RELEASE = `위키(Wiki)와 릴리스(Release) — 저장소의 '설명서'와 '출시 앨범'
위키 = 저장소에 딸린 공동 노트
· 저장소 상단 [Wiki] 클릭 페이지를 자유롭게 추가
· 코드가 아닌 지식을 담아요: 개발 환경 세팅법, 회의 결정 사항,
자주 겪는 에러 해결법(트러블슈팅 모음)
· 마크다운으로 작성 학습 플랫폼 글쓰기와 같은 문법!
릴리스 = "여기까지가 v1.0" 하고 도장 찍기
· 상단 [Releases] 특정 커밋에 버전 태그(v1.0.0) 붙이고
변경 요약(체인지로그) 적어 발표
· 게임 패치노트와 같아요: "이번 버전에서 뭐가 바뀌었나"
코드를 읽는 사람도 있게 정리한 .`;
const CODE_FIRST_ISSUE = `실습: 내 첫 이슈 만들기 — 5분 완성 레시피
1) edu.awesomedevapp.com:3000 로그인
2) 연습용 저장소(멘토가 알려준 onboarding 저장소) 이동
3) 상단 [Issues] 초록색 [New Issue] 버튼
4) 작성:
제목: [자기소개] 26 수습 입니다
본문:
안녕하세요! 오늘 Gitea 코스를 마친 입니다.
- 배운 신기했던 : ( 가지)
- 궁금한 : ( 가지)
확인 부탁드려요 @멘토아이디
5) [Create Issue] 클릭 !
포인트: 본문에 @아이디 치는 순간 자동완성 목록이 떠요.
멘션(@) 사람에게는 알림이 갑니다 카톡에서 친구를
@태그하는 것과 똑같아요. 답글이 달리면 나에게도 알림이 옵니다.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '우리 Git 서버 투어' },
{ n: 2, label: '저장소 화면 읽기' },
{ n: 3, label: '이슈로 할 일 관리' },
{ n: 4, label: 'PR과 리뷰 화면' },
{ n: 5, label: 'diff 읽는 법' },
{ n: 6, label: '프로필 꾸미기' },
{ n: 7, label: '위키와 릴리스' },
{ n: 8, label: '실습: 첫 이슈' },
];
export default function ToolGiteaPage() {
return (
<div>
{/* 히어로: Gitea를 코드 창고가 아닌 협업 본부로 */}
<div className="hero">
<div className="eyebrow">Course · 부록 · 도구</div>
<h1>Gitea 200% 활용</h1>
<p>
Gitea는 코드를 넣어 두는 창고가 아니라, 어썸데브 개발팀이 매일 모여 일하는
<strong> 협업 본부</strong>예요. 코스에서는 화면을 하나하나 읽는 법부터
이슈·Pull Request·리뷰까지, 우리 서버(<span className="icode">edu.awesomedevapp.com:3000</span>)에서
직접 손을 움직이며 익힙니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">직접 해보기 3</span>
<span className="chip">준비물: Gitea 계정</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="우리 Git 서버 투어" sub="edu.awesomedevapp.com:3000 — 회사 개발팀의 타임라인">
<p>
GitHub는 들어봤죠? <strong>Gitea</strong> GitHub와 같은 종류의 서비스를
<strong> 우리 회사 서버에 직접 설치해서 쓰는 </strong>이에요. 남의 건물(GitHub)
들어 사는 대신, 우리 건물(AWS EC2 Docker 컨테이너) 우리만의 작업실을
차린 거죠. 그래서 회사 코드가 바깥으로 나가지 않아요.
</p>
<Code>{CODE_GITEA_MAP}</Code>
<p>
로그인하면 보이는 <strong>대시보드</strong> 회사 개발팀의 SNS 피드 같은 곳이에요.
"김멘토님이 mirim-app에 푸시했습니다", "이슈 #12가 닫혔습니다" 팀이 지금 하고
있는지 피드만 봐도 감이 잡힙니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 <span className="icode">edu.awesomedevapp.com:3000</span>
로그인해서 대시보드 활동 피드를 위에서 아래로 읽어 보세요. 가장 최근에 움직인 저장소는
무엇이고, 누가 무슨 작업을 했나요? 모르는 단어(push, merge...) 나오면 메모해 두세요
코스가 끝나면 전부 읽을 있게 됩니다.
</div>
</Section>
<Section n={2} title="저장소 화면 읽기" sub="커밋 히스토리 · 브랜치 · 파일 탐색 — 3대 정보">
<p>
저장소(Repository) 클릭하면 나오는 화면은 정보가 빽빽해서 처음엔 어지러워요.
하지만 읽어야 군데입니다.
</p>
<Code>{CODE_REPO_SCREEN}</Code>
<p>
<strong>커밋 히스토리</strong> 프로젝트의 일기장이라고 했죠. 프로젝트에
투입됐을 가장 빨리 파악하는 방법이 바로 <strong>최근 커밋 메시지 20개를
읽는 </strong>이에요. "아, 요즘 이 팀은 로그인 기능을 고치고 있구나"
코드 읽고도 보입니다.
</p>
<p>
<strong>브랜치</strong> 평행우주 비유를 기억하세요. <span className="icode">main</span>
언제나 동작해야 하는 정본 우주라서, 기능은 반드시 별도 우주(브랜치)에서 실험한
검사(리뷰) 통과해야 정본에 합류합니다 합류 절차가 섹션 4 Pull Request예요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 우리 학습 플랫폼 저장소(<span className="icode">mirim-app</span>)
들어가 Commits를 클릭하고, 최근 커밋 10개의 메시지만 읽어 보세요.
<span className="icode">feat:</span>, <span className="icode">fix:</span> 같은 접두어가
보이나요? feat은 기능, fix는 버그 수정 메시지 단어만으로 작업 종류를
있게 팀의 약속입니다.
</div>
</Section>
<Section n={3} title="이슈로 할 일 관리" sub="티켓이 곧 이슈 — 말로 하면 사라지고, 이슈로 쓰면 남는다">
<p>
"버튼이 이상해요"라고 말로 전하면 순간엔 전달되지만, 다음 날이면 잊혀요.
그래서 개발팀은 모든 일을 <strong>이슈(Issue)</strong>라는 티켓으로 만듭니다.
병원 접수증과 같아요 번호가 붙고(#42), 담당자가 정해지고, 처리가 끝나면
닫힙니다(Closed). 접수증이 있으니 "그 일 어떻게 됐지?" 사라지죠.
</p>
<Code>{CODE_ISSUE_ANATOMY}</Code>
<p>
좋은 이슈의 조건은 <strong>남이 읽고 바로 움직일 있는가</strong>예요.
"안 돼요"보다는 "모바일 크롬에서 로그인 버튼이 화면 밖으로 잘려요.
스크린샷 첨부합니다" 재현 방법과 증거가 있으면 고치는 사람의 시간이
절반으로 줄어듭니다. 어썸데브에서는 여러분이 받을 과제와 발견한 버그가
전부 이슈로 오갈 거예요.
</p>
<div className="warn">
<b>이슈 제목 줄이 절반이다</b> "질문 있어요"(X) 대신
"회원가입 페이지에서 비밀번호 규칙이 어디 정의돼 있나요?"(O)처럼,
제목만 읽어도 내용이 짐작되게 쓰세요. 목록에서 보이는 제목뿐이니까요.
</div>
</Section>
<Section n={4} title="Pull Request와 리뷰 화면" sub="제 작업, 정본에 합쳐 주세요 — 검사받고 합치는 문화">
<p>
브랜치(평행우주)에서 작업을 마쳤다면, 이제 정본(<span className="icode">main</span>)
합쳐야죠. 그런데 아무나 마음대로 합치면 정본이 망가질 있으니, 팀은
<strong> Pull Request(PR)</strong>라는 절차를 둡니다. " 작업 주시고,
괜찮으면 합쳐 주세요"라는 공개 요청서예요.
</p>
<Code>{CODE_PR_FLOW}</Code>
<p>
만드는 법은 간단해요. 브랜치를 푸시하면 Gitea가 저장소 상단에
<strong> "New Pull Request"</strong> 안내를 띄워 줍니다. 클릭
<strong> 어느 브랜치를(base compare)</strong> 합칠지 확인 제목과 설명 작성
생성. 설명에는 "뭘 했고, 왜 했고, 어떻게 확인하면 되는지" 적으면 리뷰어가
기뻐합니다.
</p>
<p>
리뷰어 입장에선 <strong>Files Changed</strong> 탭이 주무대예요. 바뀐 번호에
마우스를 올리면 나타나는 <span className="icode">+</span> 버튼으로 <strong> 줄에
정확히 코멘트</strong> 있어요. 교과서 여백에 포스트잇을 붙이는 것과 같죠.
보고 나면 <strong>Approve</strong>(승인) 또는 <strong>Request changes</strong>(수정
요청) 선택합니다. 승인이 모이면 <strong>Merge</strong> 버튼으로 정본에 합쳐지고,
PR은 보라색 "Merged" 상태가 돼요.
</p>
<div className="tip">
<b>리뷰는 사람 검사가 아니라 코드 검사</b> 코멘트를 받으면 "내가 혼났다" 아니라
"코드가 더 좋아질 기회" 읽으세요. 어썸데브의 시니어들도 매일 서로의 PR에
코멘트를 주고받아요. 리뷰가 없는 코드가 오히려 무서운 코드입니다.
</div>
</Section>
<Section n={5} title="커밋 diff 읽는 법" sub="빨강과 초록의 언어 — 바뀐 것만 골라 보여주는 화면">
<p>
커밋이나 PR을 클릭하면 나오는 알록달록한 화면이 <strong>diff</strong>(차이)예요.
파일 전체가 아니라 <strong>바뀐 부분만</strong> 보여줍니다. 숙제 첨삭본과 같아요
빨간 (지운 ) 초록 (새로 ) 보면 뭐가 달라졌는지 바로 보이죠.
</p>
<Code>{CODE_DIFF_READ}</Code>
<p>
diff를 읽을 알면 개발 생활이 달라져요. 동료의 커밋에서 배우고,
"어제까지 되던 게 왜 안 되지?" <strong>마지막 커밋의 diff부터</strong>
확인하는 습관이 생깁니다. 범인은 대부분 가장 최근에 바뀐 줄에 있거든요.
</p>
<div className="tip">
<b>직접 확인해 보기</b> <span className="icode">mirim-app</span> 저장소에서 아무 커밋이나
클릭해 diff를 열어 보세요. 빨강·초록 쌍을 찾아 "뭐가 뭐로 바뀌었는지" 소리 내어
설명해 보고, 상단의 "N files changed, +x y" 요약과 실제 내용이 맞는지
세어 보세요. 설명이 되면 diff 읽기 합격!
</div>
</Section>
<Section n={6} title="프로필 꾸미기" sub="아바타·이름·잔디밭 — 온라인 명함 정리하기">
<p>
Gitea에서 여러분은 아이디로만 존재해요. 그런데 리뷰 코멘트, 이슈 담당자, 커밋
기록 모든 곳에 프로필이 따라다닙니다. 아바타가 기본 이미지에 이름도 비어 있으면
동료가 "이 사람 누구지?" 하게 되죠. <strong>프로필은 온라인 명함</strong>입니다.
출근 자리 정리하듯, 처음에 5분만 투자해 정리해 두세요.
</p>
<Code>{CODE_PROFILE}</Code>
<p>
<strong>잔디밭(활동 히트맵)</strong> 게임의 출석 보상판 같은 재미가 있어요.
커밋·이슈·PR 활동이 있는 날마다 칸이 초록으로 채워지는데, 억지로 채우는
목표가 아니라 <strong>꾸준함이 눈에 보인다</strong> 핵심이에요. 수습이 끝날
자신의 잔디밭을 스크린샷해 두면 훌륭한 성장 기록이 됩니다.
</p>
</Section>
<Section n={7} title="위키와 릴리스 한 입" sub="코드 밖의 지식(위키)과 버전의 이정표(릴리스)">
<p>
저장소에는 코드 말고도 개의 부속 공간이 있어요. 자주 쓰진 않아도
<strong> 존재를 알아야 찾아갈 있는</strong> 곳들입니다.
</p>
<Code>{CODE_WIKI_RELEASE}</Code>
<p>
위키는 "이거 어떻게 하더라?" 반복될 쓰는 곳이에요. 같은 질문을
받았다면, 답을 위키에 적어 두고 다음부터는 링크를 주면 됩니다.
릴리스는 반대로 <strong>바깥을 향한 발표</strong> "v1.2 나왔고, 이런
바뀌었습니다" 코드 모르는 사람도 읽을 있게 남기는 기록이에요.
</p>
<div className="warn">
<b>위키 코드 저장소</b> 위키에 적은 내용은 코드 리뷰(PR) 거치지 않고
바로 수정돼요. 편한 만큼 책임도 함께입니다. 확실하지 않은 내용은
"(확인 필요)"라고 표시해 두는 습관을 들이세요.
</div>
</Section>
<Section n={8} title="실습: 내 첫 이슈 만들고 멘토 멘션하기" sub="배운 것을 실제 티켓 한 장으로 — 오늘의 졸업 과제">
<p>
이제 배운 전부 씁니다. 이슈를 하나 만들고(섹션 3), 본문에서 멘토를
<strong> 멘션(@)</strong>해서 알림을 보내는 것까지 장의 티켓이
여러분의 Gitea 발자국이자, 잔디밭(섹션 6) 초록 칸이 됩니다.
</p>
<Code>{CODE_FIRST_ISSUE}</Code>
<ol className="olist">
<li>이슈를 만든 , 목록에서 이슈가 <strong>Open</strong> 상태로 보이는지 확인해요.</li>
<li>멘토의 답글 알림이 오면(상단바 모양), 댓글을 달아 보세요 이슈는 대화 스레드라는 체험하게 됩니다.</li>
<li>대화가 끝나면 멘토가 이슈를 <strong>Close</strong> 거예요. 열리고 오가고 닫히는 이슈의 한살이를 완주한 겁니다!</li>
</ol>
<div className="tip">
<b>직접 확인해 보기(심화)</b> 이슈를 만들 오른쪽 사이드바에서
<strong> 라벨(Label)</strong> 하나 붙여 보세요(: <span className="icode">question</span>).
그리고 이슈 목록 상단의 필터에서 라벨로 걸러 보면 라벨이 '색깔 태그'라고
불리는지, 이슈가 수백 개가 돼도 무서운지 감이 옵니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🦊 여기까지 왔다면</h3>
<p className="muted">
이제 Gitea는 코드 창고가 아니라 <strong>일이 흐르는 본부</strong> 보일 거예요
일은 이슈로, 작업은 브랜치로, 합류는 PR과 리뷰로, 변화는 diff로 읽는 흐름을
전부 손으로 해봤으니까요. 흐름의 밑바닥에서 도는 Git 자체가 궁금하다면{' '}
<Link to="/learn/git"><strong>Git 기초</strong></Link> 코스로, 방금 만든 이슈처럼
실제 과제를 받아 PR까지 완주해 보고 싶다면{' '}
<Link to="/assignments"><strong>과제 게시판</strong></Link>에서 티켓을
골라 보세요. 여러분의 다음 초록 칸을 기다릴게요.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,374 @@
// : " "
// · · ··· 8 ,
// TIL 릿 .
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// · JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_THREE_LINES = `3줄 회고 — 매일 퇴근(하교) 전 3분이면 충분해요.
오늘 배운 : Docker 컨테이너와 이미지의 차이 (이미지=붕어빵 , 컨테이너=붕어빵)
오늘 막힌 : Gitea에 push했는데 인증 오류 토큰 만료가 원인이었음
내일 : Spring Boot 컨트롤러에 API 하나 추가해 보기
3줄이 100 쌓이면? 나만의 성장 그래프이자
"나 이런 것까지 해봤어요"라고 보여줄 있는 포트폴리오가 됩니다.`;
const CODE_BLOCKS = `노션의 4가지 기본 블록 — 이것만 알면 시작할 수 있어요.
블록 단축키/입력법 언제 쓰나
텍스트 그냥 타이핑 기본 메모, 회고 문장
토글 > + 스페이스 접었다 펼치는 목록 "답 가리기" 최고
코드 \`\`\` 입력 명령어·설정·에러 메시지 원본 보존
(테이블) / 입력 비교표, 체크리스트
슬래시(/) .
"뭐가 있는지 모르겠으면 일단 /" 노션의 만능 열쇠입니다.`;
const CODE_PAGE_TREE = `추천 페이지 구조 — 폴더 정리하듯 페이지를 트리로 쌓아요.
📓 나의 개발 노트 (최상위 페이지)
📅 TIL 일지 매일 3 회고가 쌓이는
2026-07-16
2026-07-15
...
🔥 트러블슈팅 모음 "막혔다가 뚫은 기록" 따로
Gitea push 인증 오류 해결
Docker 포트 충돌 해결
명령어 치트시트 자주 까먹는 명령어 사전
Git 명령어
Docker 명령어
핵심: '일지' '해결법' 섞지 마세요.
일지는 날짜순, 해결법은 주제순 찾는 방법이 다르니까요.`;
const CODE_TROUBLE_TEMPLATE = `트러블슈팅 페이지 템플릿 — 이 4칸만 채우면 됩니다.
## 증상
Gitea(edu.awesomedevapp.com:3000) push하면
"authentication failed" 에러가
## 원인
액세스 토큰이 만료되어 있었음
## 해결
Gitea 설정 애플리케이션 토큰 발급 다시 push
## 배운
에러 메시지를 그대로 검색하면 답이 빨리 나온다.
토큰에도 유효기간이 있다!`;
const CODE_SNIPPET = `코드 블록 활용 예 — 자주 쓰는 명령어를 '복사 가능한 원본'으로 저장.
# 우리 회사에서 매일 쓰는 Docker 명령어
docker ps # 지금 돌고 있는 컨테이너 목록
docker compose up -d # 백그라운드로 전체 스택 실행
docker logs -f 컨테이너이름 # 로그 실시간으로 보기
노션 코드 블록에 마우스를 올리면 '복사' 버튼이 떠요.
스크린샷으로 찍은 명령어는 다시 타이핑해야 하지만,
코드 블록은 클릭 번이면 터미널에 붙여넣기 !`;
const CODE_DATABASE = `과제 트래커 데이터베이스 — 표에 '속성'이 붙으면 데이터베이스가 됩니다.
과제 이름 상태 마감일 과목
React 컴포넌트 과제 진행 07-18 프론트엔드
PostgreSQL 테이블 설계 완료 07-14 데이터베이스
학습 플랫폼 회고 작성 시작 07-21 공통
데이터베이스가 표보다 강한 이유:
1) 필터 "상태 = 진행 중" 골라 보기
2) 정렬 마감일 빠른 순으로 자동 세우기
3) 보기 같은 데이터를 표로도, 칸반 보드로도, 캘린더로도!`;
const CODE_SEARCH_RULE = `검색되는 기록을 만드는 3가지 규칙
1) 에러 메시지는 '복사-붙여넣기' 원본 그대로
나쁨: "빨간 에러가 났다"
좋음: "port is already allocated" 에러 발생
나중에 같은 에러가 나면 문구로 검색해서 바로 찾음!
2) 제목에 핵심 단어를 넣기
나쁨: "오늘의 삽질"
좋음: "Docker 포트 충돌(port is already allocated) 해결"
3) 미래의 나를 독자로 생각하기
3개월 뒤의 나는 오늘의 맥락을 까먹은 남입니다.
"그때 그거" 아니라 "무엇을, 왜, 어떻게" 적어 주세요.`;
const CODE_TIL_TEMPLATE = `나만의 TIL 템플릿 — 이대로 만들고 매일 복제해서 쓰세요.
# 📅 2026-07-16 ()
## 오늘 배운 (Today I Learned)
-
## 오늘 막힌 & 해결 과정
> 토글 블록으로 만들면: 제목만 보고 "이거 뭐였더라?" 떠올려
> 펼쳐서 답을 확인하는 셀프 퀴즈가 됩니다.
## 오늘의 명령어/코드 조각
(코드 블록으로! 스크린샷 금지)
## 내일
- [ ]
## 오늘의 기분
`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '성장 = 기록' },
{ n: 2, label: '기본 블록' },
{ n: 3, label: '페이지 구조' },
{ n: 4, label: '스니펫 관리' },
{ n: 5, label: '데이터베이스' },
{ n: 6, label: '검색되는 기록' },
{ n: 7, label: '공유와 협업' },
{ n: 8, label: '실습: TIL 템플릿' },
];
export default function ToolNotionPage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 부록 · 도구</div>
<h1>노션으로 기록하기</h1>
<p>
같은 배워도 <strong>적어 사람</strong> 적어 사람의 3개월 뒤는
완전히 달라요. 코스에서는 노션으로 배운 ·막힌 ·해결한 것을 차곡차곡
쌓아서, <strong>검색 번이면 꺼내 쓰는 나만의 개발 노트</strong> 만듭니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 40</span>
<span className="chip">직접 해보기 3</span>
<span className="chip">준비물: 노션 계정 (무료)</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="개발자의 성장 = 기록의 축적" sub="머리는 휘발되고, 기록은 복리로 쌓인다">
<p>
개발을 배우다 보면 하루에도 새로운 것이 쏟아져요. React 문법, Docker 명령어,
Gitea 사용법... 그런데 우리 뇌는 <strong>USB가 아니라 휘발성 메모리(RAM)</strong>
가깝습니다. 전원이 꺼지면(자고 일어나면) 상당 부분이 날아가죠. 그래서 잘하는
개발자들은 예외 없이 <strong>어딘가에 적어 두는 습관</strong> 있어요.
</p>
<p>
거창할 필요 없어요. 매일 <strong>3 회고</strong> 충분합니다. 문제는
"어디에" 적느냐예요. 카톡 나에게 보내기? 메모장 파일? 흩어져서 나중에
찾아요. 코스에서 도구가 바로 <strong>노션(Notion)</strong>
메모장 + 폴더 + + 검색이 몸인 기록 앱입니다.
</p>
<Code>{CODE_THREE_LINES}</Code>
<div className="tip">
<b> 기록이 '복리'일까요?</b> 오늘 적은 해결법은 다음에 같은 문제를 만났을
검색 10초로 재사용돼요. 적금처럼 넣은 만큼만 돌아오는 아니라, 꺼내 쓸수록
시간이 이자처럼 불어납니다.
</div>
</Section>
<Section n={2} title="노션 기본 블록 4가지" sub="텍스트 · 토글 · 코드 · 표 — 레고 블록처럼 조립">
<p>
노션의 모든 내용은 <strong>블록</strong>이라는 단위로 이뤄져 있어요. 레고처럼
블록을 쌓아 페이지를 만드는 거죠. 종류가 수십 가지지만, 개발 기록에는
4가지면 90% 해결됩니다.
</p>
<Code>{CODE_BLOCKS}</Code>
<p>
특히 <strong>토글 블록</strong> 개발 노트의 숨은 보석이에요. "Docker 이미지와
컨테이너의 차이는?"이라고 제목을 쓰고 답을 토글 안에 접어 두면, 나중에 제목만
보고 스스로 답해 펼쳐서 확인하는 <strong>셀프 퀴즈</strong> 됩니다.
</p>
<div className="tip">
<b>직접 해보기 </b> 노션에서 페이지를 만들고 4가지 블록을 하나씩 넣어 보세요.
<span className="icode">/토글</span> 입력해 토글 블록을 만들고, 제목에
"우리 학습 플랫폼의 프론트는 무엇으로 만들어졌을까?"라고 안에
"React!" 접어 넣어 보세요. 셀프 퀴즈 완성입니다.
</div>
</Section>
<Section n={3} title="페이지 구조 잡기" sub="TIL 일지 · 트러블슈팅 모음 · 명령어 치트시트">
<p>
블록을 배웠으니 이제 <strong>집의 뼈대</strong> 세울 차례예요. 노션 페이지는
안에 페이지를 넣을 있어서, 폴더처럼 트리 구조를 만들 있습니다.
수습 기간에 추천하는 구조는 채예요.
</p>
<Code>{CODE_PAGE_TREE}</Code>
<p>
<strong>트러블슈팅 모음</strong> 특히 중요해요. "막혔다가 뚫은 경험"
여러분의 실력이 가장 크게 자라는 순간인데, 적어 두면 다음에 똑같은 곳에서
막혀요. 형식은 아래 4칸이면 충분합니다.
</p>
<Code>{CODE_TROUBLE_TEMPLATE}</Code>
<div className="warn">
<b>흔한 실수</b> 처음부터 폴더를 10, 20 만들어 놓는 . 서랍장만 잔뜩
놓고 옷은 넣는 격이에요. 위의 <strong> 페이지로 시작</strong>하고,
기록이 넘칠 나누세요. 구조는 기록을 따라오는 것이지, 기록보다 먼저가 아닙니다.
</div>
</Section>
<Section n={4} title="코드 블록으로 스니펫 관리" sub="스크린샷은 죽은 기록, 코드 블록은 살아 있는 기록">
<p>
명령어나 설정을 기록할 초보자가 가장 많이 하는 실수가 <strong>스크린샷으로
찍어 두는 </strong>이에요. 스크린샷 명령어는 복사가 되고, 검색도 되고,
글자가 흐릿하면 읽지도 못해요. 반드시 <strong>코드 블록</strong> 텍스트로
저장하세요.
</p>
<Code>{CODE_SNIPPET}</Code>
<p>
코드 블록을 만들 <strong>언어를 지정</strong>하면(Shell, SQL, JavaScript )
문법에 맞게 색이 입혀져 훨씬 읽기 좋아요. 우리 회사 스택 기준으로는
Git·Docker 명령어(Shell), PostgreSQL 쿼리(SQL), React 코드(JavaScript)
각각의 언어로 지정해 모아 두면 나만의 치트시트가 완성됩니다.
</p>
<div className="tip">
<b>직접 해보기 </b> 터미널을 열어 <span className="icode">git log --oneline -5</span>
실행하고, 명령어와 결과를 노션 코드 블록(언어: Shell) 붙여 넣어 보세요.
그리고 코드 블록에 마우스를 올려 <strong>복사 버튼</strong> 눌러 터미널에
다시 붙여넣기 왕복이 되는 순간, 스크린샷과의 차이를 몸으로 느낄 거예요.
</div>
</Section>
<Section n={5} title="데이터베이스 맛보기 — 과제 트래커" sub="표에 필터·정렬·보기가 붙으면 데이터베이스">
<p>
노션의 <strong>데이터베이스</strong> 이름이 거창하지만, 시작은 그냥
<strong> 속성(컬럼) 붙은 </strong>예요. 우리가 배우는 PostgreSQL의 테이블과
닮았죠 (row) 하나가 데이터 하나, (column) 하나가 속성 하나. 다만 노션은
SQL 대신 마우스 클릭으로 필터와 정렬을 겁니다.
</p>
<Code>{CODE_DATABASE}</Code>
<p>
만들기는 간단해요. <span className="icode">/데이터베이스</span> 입력하고
"표 보기" 선택한 , 속성으로 <strong>상태(선택)·마감일(날짜)·과목(선택)</strong>
추가하면 . 같은 데이터를 <strong>칸반 보드 보기</strong> 바꾸면 과제 카드를
"시작 전 → 진행 중 → 완료" 드래그하며 옮길 있어서, 일이 눈에 들어옵니다.
</p>
<div className="tip">
<b>연결해서 생각하기</b> 나중에 <Link to="/learn/database"><strong>데이터베이스
코스</strong></Link>에서 PostgreSQL을 배울 ", 노션 데이터베이스의 필터가
SQL의 WHERE고, 정렬이 ORDER BY구나"라고 연결되는 순간이 거예요.
도구는 달라도 데이터를 다루는 사고방식은 하나입니다.
</div>
</Section>
<Section n={6} title="검색되는 기록이 진짜 자산" sub="적는 것의 절반, 찾을 수 있게 적는 것이 나머지 절반">
<p>
열심히 적어도 <strong> 찾으면 없는 기록</strong>이에요. 도서관에 책이 아무리
많아도 색인이 없으면 창고일 뿐이죠. 노션은 <span className="kbd">Ctrl</span>+
<span className="kbd">P</span> 번으로 전체 페이지를 검색할 있는데,
검색이 걸리게 적는 요령이 따로 있습니다.
</p>
<Code>{CODE_SEARCH_RULE}</Code>
<p>
핵심은 <strong>"3개월 뒤의 나는 남이다"</strong>라는 마음가짐이에요. 남이
검색창에 뭐라고 칠지 상상하며 제목과 본문에 단어를 심어 두는
이게 기록을 메모에서 <strong>자산</strong>으로 바꾸는 끗입니다.
</p>
<div className="warn">
<b>주의</b> 회사 코드나 시크릿(비밀번호, 토큰, <span className="icode">.env</span> 내용)
개인 노션에 통째로 복사하면 돼요. 기록하고 싶다면 값은
<span className="icode">****</span> 가리고 "어디에 있는지, 어떻게 발급받는지"
적으세요. 기록 습관만큼 보안 습관도 개발자의 기본기입니다.
</div>
</Section>
<Section n={7} title="공유와 협업" sub="내 노트가 팀의 노트가 되는 순간">
<p>
노션의 모든 페이지는 <strong>링크 하나로 공유</strong> 있어요. 오른쪽
"공유" 버튼에서 특정 사람을 초대하거나, 링크가 있는 사람 모두 보게 있죠.
권한도 <strong>보기 전용 / 댓글 가능 / 편집 가능</strong> 단계로 나뉩니다
Gitea에서 저장소 권한을 나누는 것과 같은 개념이에요.
</p>
<p>
수습 기간에 특히 좋은 활용법 가지예요. 첫째, <strong>트러블슈팅 페이지를
동기와 공유</strong>하기 내가 어제 뚫은 문제를 동기가 오늘 만나는 일이 정말
많거든요. 서로의 삽질 기록이 전체의 지도가 됩니다. 둘째, <strong>멘토에게
질문할 정리한 페이지 링크로 물어보기</strong> "안 돼요"보다 "증상·시도한 ·
에러 메시지를 정리했는데 주세요" 빠른 답을 받아요.
</p>
<div className="tip">
<b>질문의 품격</b> 정리된 질문 페이지는 자체로 "이 사람 일 잘하겠다"
신호예요. 신기하게도, 질문을 정리하다가 스스로 답을 찾는 일도 자주 생깁니다.
남에게 설명하려고 정리하는 순간 머릿속이 정리되는 이걸 러버덕 효과라고 불러요.
</div>
</Section>
<Section n={8} title="실습: 나만의 TIL 템플릿 만들기" sub="오늘 배운 것을 오늘 첫 기록으로">
<p>
이제 배웠으니 <strong>진짜로 만들 시간</strong>이에요. 아래 템플릿을 참고해서
여러분만의 TIL 페이지를 만들고, <strong>바로 오늘, 코스에서 배운 </strong>
기록으로 남겨 보세요.
</p>
<Code>{CODE_TIL_TEMPLATE}</Code>
<ol className="olist">
<li>노션에 <strong>"TIL 일지"</strong> 페이지를 만들어요. (섹션 3 구조 참고)</li>
<li> 안에 오늘 날짜로 페이지를 만들고, 템플릿의 항목을 블록으로 조립해요.
막힌 항목은 <strong>토글 블록</strong>, 명령어 항목은 <strong>코드 블록</strong>으로!</li>
<li>"오늘 배운 것" 코스에서 기억에 남는 3가지를 적어요.
(: 토글로 셀프 퀴즈 만들기, 스크린샷 대신 코드 블록, 3개월 뒤의 나는 남이다)</li>
<li> 만든 페이지에서 <span className="icode">...</span> 메뉴
<strong> 복제</strong> 눌러 보세요. 내일부터는 복제 번으로 일지를 시작할 있어요.</li>
<li>마지막으로 <span className="kbd">Ctrl</span>+<span className="kbd">P</span> 눌러
방금 단어 하나로 페이지가 검색되는지 확인 검색되면 진짜 완성입니다.</li>
</ol>
<div className="tip">
<b>직접 해보기 (오늘의 미션)</b> 5단계를 끝내고, 완성한 TIL 페이지의
공유 링크를 멘토에게 보내 보세요. "수습 기간 동안 매일 여기에 기록하겠습니다"라는
줄과 함께요. 공개 선언은 습관을 만드는 가장 강력한 접착제입니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>📓 여기까지 왔다면</h3>
<p className="muted">
이제 여러분에겐 <strong>배운 것이 사라지지 않는 시스템</strong> 생겼어요.
블록으로 적고, 구조로 정리하고, 검색으로 꺼내 쓰고, 링크로 나눈다
가지가 오늘의 전부입니다. 앞으로 모든 코스에서 배우는 내용을
오늘 만든 TIL에 쌓아 보세요. 다음 도구가 궁금하다면{' '}
<Link to="/learn/git"><strong>Git과 Gitea로 협업하기</strong></Link> 코스에서
"코드의 기록" 남기는 법을 이어서 배우고, 코스의 과제를
오늘 만든 <strong>과제 트래커</strong> 등록해 관리해 보세요.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,399 @@
// : " "
// , 7 .
// ( 30 )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_WINDOW = `창 관리 — 화면 위의 창들을 손끝으로 배치하기
단축키 하는
Win + / Win + 창을 화면 왼쪽/오른쪽 절반에 붙이기(스냅)
Win + / Win + 최대화 / 원래 크기 최소화
Win + Z 스냅 레이아웃 열기 (3분할·4분할도 가능)
Alt + Tab 열린 사이를 순서대로 전환 (누른 Tab 반복)
Win + Tab 작업 보기 모든 + 가상 데스크톱 한눈에
Win + D 모든 최소화 바탕화면 (다시 누르면 복귀)
Win + L 화면 잠금 (자리 비울 반드시! 회사 보안 기본기)
Win + Shift + S 화면 캡처 (영역 지정 스크린샷)
가상 데스크톱 "책상을 여러 개 쓰는" 기능
Win + Ctrl + D 가상 데스크톱 만들기
Win + Ctrl + / 데스크톱 사이 이동
Win + Ctrl + F4 현재 가상 데스크톱 닫기
추천 배치: 1 책상 = 코딩(에디터+터미널),
2 책상 = 자료(브라우저·문서), 3 책상 = 메신저`;
const CODE_TEXT = `텍스트 편집 — 코드를 다룰수록 몸값이 오르는 조합
단축키 하는
Ctrl + / 단어 단위로 커서 점프 ( 글자씩 ×, 단어씩 )
Ctrl + Shift + / 단어 단위로 선택
Home / End 줄의 / 끝으로
Shift + Home / End 커서부터 /끝까지 통째로 선택
Ctrl + Home / End 문서의 / 아래로
Ctrl + A 전체 선택
Ctrl + Z / Ctrl + Y 실행 취소 / 다시 실행
Ctrl + Backspace 단어 하나를 통째로 지우기
조합 공식: Ctrl = "단어/문서 단위로 크게",
Shift = "이동하면서 선택까지" 둘을 섞으면 곱셈이 됩니다.
) Ctrl+Shift+End = 커서부터 문서 끝까지 전부 선택!`;
const CODE_TABS = `탐색기 · 브라우저 — 매일 수백 번 하는 동작들
단축키 하는
Win + E 파일 탐색기 열기
Alt + 탐색기에서 상위 폴더로
F2 파일 이름 바꾸기 (더블클릭-클릭 곡예 금지!)
Ctrl + T 브라우저
Ctrl + W 현재 닫기
Ctrl + Shift + T 방금 닫은 되살리기 (실수 복구의 은인)
Ctrl + Tab 다음 탭으로 / Ctrl+Shift+Tab 이전 탭으로
Ctrl + 1 ~ 8 n번째 탭으로 바로 점프 (Ctrl+9 = 마지막 )
Ctrl + L 주소창으로 커서 이동 (F6도 동일)
Ctrl + F 페이지 안에서 글자 찾기
F5 / Ctrl + Shift + R 새로고침 / 캐시 무시하고 강력 새로고침`;
const CODE_CLIPBOARD = `Win + V — 클립보드 히스토리
보통의 복사-붙여넣기: 클립보드에 "마지막 1개" 산다
Win + V의 세계: 최근 복사한 것들이 목록으로 있다
처음 번만: Win + V "켜기" 버튼 클릭 (활성화)
이후에는:
Ctrl + C (여러 , 여러 곳에서 복사해 두고)
Win + V (목록에서 골라서 붙여넣기!)
보너스: 자주 쓰는 항목은 목록에서 📌 고정(pin)
재부팅해도 살아남아요. Gitea 주소, 자주 쓰는 커밋 접두어,
회사 위키 링크 같은 고정해 두면 됩니다.`;
const CODE_EMOJI = `Win + . (윈도우 키 + 마침표) — 이모지 패널
커밋 메시지, 리뷰 코멘트, 메신저 어디서든:
Win + . 검색창에 "체크" / "불" / "박수" 입력 Enter
이모지만 있는 아니에요. 패널 상단 탭을 보면:
😀 이모지 | Ω 기호(특수문자) | ; 콜론(카오모지)
화살표, ±, , · 같은 특수문자를
"ㅁ + 한자키" 곡예 없이 검색해서 넣을 있습니다.`;
const CODE_AUTOHOTKEY = `자동 문자열 치환 — "자주 치는 긴 문장"을 약어로
아이디어: 특정 약어를 치면 문자열로 자동 확장.
)
;; 치면 wnrud2222@awesomedev.dev 같은 자주 쓰는 주소
gtea 치면 edu.awesomedevapp.com:3000
ㅈㅅ 치면 "확인했습니다. 감사합니다!"
도구 예시:
- PowerToys (마이크로소프트 공식, Keyboard Manager 포함)
- AutoHotkey (스크립트로 무엇이든 진입장벽은 있음)
- Espanso (오픈소스 텍스트 확장 전용, 설정이 파일이라 Git 관리 가능)
주의: 회사 PC에 도구를 설치할 먼저 멘토에게 확인!
설치 없이도 Win+V 고정(pin)으로 절반은 흉내 있어요.`;
const CODE_MISSION = `실습 미션 1 — "오늘 하루 마우스 없이 창 전환"
규칙: 오늘 퇴근(하교)까지, 창을 바꿀 마우스로
작업 표시줄을 클릭하면 벌점 1. 대신:
전환 Alt + Tab
배치 Win + /
바탕화면 보기 Win + D
탐색기 열기 Win + E
이동 Ctrl + Tab / Ctrl + 숫자
목표: 벌점 5 이하로 하루 버티기.
처음엔 답답해서 손이 근질근질할 거예요 정상입니다.
3일째부터 손이 먼저 움직이기 시작해요.`;
const CODE_BINGO = `실습 미션 2 — 단축키 빙고 (5×5)
오늘 실제 업무/학습 중에 "자연스럽게" 칸에 O 표시.
가로·세로·대각선 줄이면 빙고!
Win+/ Alt+Tab Ctrl+W Win+V Ctrl+L
Ctrl+ Win+E F2 Ctrl+ Win+.
Shift+T Shift+S*
Ctrl+/ Win+D 무료칸 Ctrl+Tab Win+L
Ctrl+ Home/End Win+Z Ctrl+F Ctrl+
Backspace 숫자
Win+Tab Win+Ctrl Ctrl+ Shift+ F5
+/ Shift+End Home/End
* Ctrl+Shift+S 아님 주의 Win+Shift+S (화면 캡처)
빙고 3 완성 = 코스 실기 합격!
같이 배우는 동기가 있다면 누가 먼저 3 채우나 내기해 보세요.`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '왜 단축키인가' },
{ n: 2, label: '창 관리' },
{ n: 3, label: '텍스트 편집' },
{ n: 4, label: '탐색기·브라우저' },
{ n: 5, label: 'Win+V 클립보드' },
{ n: 6, label: 'Win+. 와 치환 도구' },
{ n: 7, label: '실습 미션' },
];
export default function ToolShortcutsPage() {
return (
<div>
{/* 히어로: 이 코스가 약속하는 것 */}
<div className="hero">
<div className="eyebrow">Course · 부록 · 도구</div>
<h1>윈도우 단축키 마스터</h1>
<p>
하루에 수백 하는 "창 바꾸기·복사·붙여넣기"에서 마우스로 왕복하는 2초씩을
아끼면, 일주일이면 시간이 됩니다. 코스는 외울 단축키를 늘어놓는 대신,
<strong> 오늘 당장 손에 붙는 30</strong> 상황별로 골라 실습 미션과 함께 익혀요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 40 + 실습 하루</span>
<span className="chip">필수 단축키 30</span>
<span className="chip">준비물: 윈도우 PC</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="마우스를 버릴수록 빨라진다" sub="키보드 → 마우스 → 키보드, 그 왕복이 문제">
<p>
타자를 치다가 창을 바꾸려고 마우스로 손을 옮기는 순간을 떠올려 보세요.
손이 이동하고, 마우스 커서를 찾고, 작업 표시줄의 작은 아이콘을 조준하고,
클릭하고, 다시 키보드로 돌아옵니다. 번에 2~3 별거 아닌 같지만
왕복을 하루에 수백 해요. <strong>요리사가 칼질 때마다
칼을 서랍에 넣었다 꺼낸다면</strong> 어떨까요? 단축키는 칼을 손에서
놓지 않는 기술입니다.
</p>
<p>
중요한 <strong>흐름(몰입)</strong>이에요. 코드를 짜다가 마우스로 손을 옮기면
머릿속 생각도 잠깐 끊깁니다. 단축키가 손에 붙으면 "창 바꿔야지"라는 생각조차 없이
손가락이 먼저 움직여요 자전거 "페달을 밟아야지" 하고 생각하지 않는 것처럼요.
</p>
<div className="olist">
<ol>
<li>이번 : 섹션 2~4 표에서 <strong>내가 자주 하는 동작 5</strong> 고른다.</li>
<li> 5개를 때마다 마우스 대신 단축키로 어색해도 참는다.</li>
<li>손에 붙으면(대략 3) 다음 5개를 추가한다.</li>
</ol>
</div>
<div className="tip">
<b>한꺼번에 30 외우려 하지 마세요</b> 단축키는 암기 과목이 아니라
<strong> 운동</strong>이에요. 하루 5개씩, 실제 상황에서 손으로 반복하는
표를 읽는 것보다 낫습니다.
</div>
</Section>
<Section n={2} title="창 관리 — Win 키의 재발견" sub="창 스냅, Alt+Tab, 그리고 가상 데스크톱">
<p>
개발할 화면에 창이 최소 셋은 있죠 에디터, 브라우저, 터미널.
창들을 마우스로 끌어서 배치하는 대신, <span className="kbd">Win</span> 키와
화살표로 <strong>착착 스냅</strong>시키는 것부터 시작합니다.
</p>
<Code>{CODE_WINDOW}</Code>
<p>
<strong>가상 데스크톱</strong> "책상을 여러 개 쓰는" 기능이에요. 공부 책상 위에
게임기가 보이면 집중이 흩어지듯, 코딩 책상(1) 메신저 책상(3) 분리해 두면
알림에 홀려 길로 새는 물리적으로 막을 있어요.
<span className="kbd">Win</span>+<span className="kbd">Ctrl</span>+<span className="kbd"></span> 번이면
책상이 통째로 바뀝니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 지금 바로 페이지에서
<span className="kbd">Win</span>+<span className="kbd"></span> 브라우저를 오른쪽 절반에 붙이고,
<span className="kbd">Win</span>+<span className="kbd">E</span> 탐색기를 열어
<span className="kbd">Win</span>+<span className="kbd"></span> 왼쪽에 붙여 보세요.
마우스 없이 2분할 화면 완성! 이어서 <span className="kbd">Win</span>+<span className="kbd">Ctrl</span>+<span className="kbd">D</span>
데스크톱을 만들고 <span className="kbd">Win</span>+<span className="kbd">Ctrl</span>+<span className="kbd"></span> 돌아와 보세요.
</div>
<div className="warn">
<b>자리를 비울 <span className="kbd">Win</span>+<span className="kbd">L</span></b>
속도 이전에 <strong>보안 기본기</strong>예요. 회사에서 화면을 잠그지 않고 자리를 뜨는
현관문을 열어 두고 외출하는 것과 같습니다. 어썸데브에서도 필수 습관!
</div>
</Section>
<Section n={3} title="텍스트 편집 — Ctrl과 Shift의 곱셈" sub="한 글자씩 ×, 한 단어씩 ○">
<p>
커서를 옮기려고 화살표 키를 다다다다 연타하고 있다면, 섹션이 인생을 바꿔요.
핵심 공식은 <span className="kbd">Ctrl</span> "<strong>단어/문서 단위로
크게 움직여라</strong>", <span className="kbd">Shift</span>는 "<strong>움직인 만큼
선택해라</strong>". 둘은 곱셈처럼 조합됩니다.
</p>
<Code>{CODE_TEXT}</Code>
<p>
조합들은 메모장·브라우저 주소창·에디터·코드 리뷰 코멘트 어디서든 똑같이 통해요.
특히 코드를 다룰 <span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd"></span>
변수 이름 하나를 정확히 선택하는 감각은, 한번 익히면 마우스 더블클릭으로 돌아갈 없습니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 메모장을 열고 아무 문장이나 보세요. 그리고 마우스에서
손을 채로 <span className="kbd">Ctrl</span>+<span className="kbd">Home</span>으로 위로,
<span className="kbd">Ctrl</span>+<span className="kbd"></span> 단어 점프,
<span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd">End</span>
거기서 끝까지 선택, <span className="kbd">Ctrl</span>+<span className="kbd">Backspace</span>
단어 하나 삭제 <span className="kbd">Ctrl</span>+<span className="kbd">Z</span> 복구.
4단계가 3 안에 되면 합격!
</div>
</Section>
<Section n={4} title="탐색기와 브라우저 — 하루 수백 번의 동작" sub="탭과 파일을 손끝에서 다루기">
<p>
개발자의 하루는 탭과의 전쟁이에요. 문서 , Gitea , 학습 플랫폼 , 검색 ...
탭들 사이를 마우스로 오가는 대신, 아래 표의 조합으로 다녀 보세요.
</p>
<Code>{CODE_TABS}</Code>
<p>
특히 <span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd">T</span>
"닫은 탭 되살리기" 실수로 중요한 문서 탭을 닫았을 때의 구조대예요. 여러 누르면
닫은 순서 역순으로 계속 되살아납니다. 그리고 <span className="kbd">F2</span>(이름 바꾸기)
탐색기에서 "천천히 두 번 클릭" 곡예를 은퇴시켜 줍니다.
</p>
<div className="tip">
<b>직접 확인해 보기</b> 브라우저에서 <span className="kbd">Ctrl</span>+<span className="kbd">T</span>
<span className="kbd">Ctrl</span>+<span className="kbd">L</span> 주소창 이동
우리 Gitea 주소 <span className="icode">edu.awesomedevapp.com:3000</span> 입력 접속되면
<span className="kbd">Ctrl</span>+<span className="kbd">W</span> 닫고
<span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd">T</span> 부활!
사이클을 마우스 없이 완주해 보세요.
</div>
</Section>
<Section n={5} title="Win + V — 클립보드 히스토리 (인생 단축키)" sub="복사한 것들이 사라지지 않는 세계">
<p>
보통의 클립보드는 <strong>기억력이 1</strong>예요. A를 복사하고 B를 복사하면
A는 증발하죠. 그래서 우리는 개를 오가며 복사-붙여넣기-복사-붙여넣기를
반복해 왔습니다. <span className="kbd">Win</span>+<span className="kbd">V</span>
세계관을 바꿔요 <strong>최근 복사한 것들이 목록으로 서서 기다립니다.</strong>
</p>
<Code>{CODE_CLIPBOARD}</Code>
<p>
수습 개발자의 실전 활용: 코드 리뷰를 받으며 고칠 부분 여러 곳을 미리
<strong> 연달아 복사</strong> 두고, 문서에 붙일 <span className="kbd">Win</span>+<span className="kbd">V</span>에서
하나씩 골라 넣기. 브랜치 이름, 커밋 해시, 에러 메시지를 동시에 들고 다닐 있어요.
장바구니에 담아 두고 계산대에서 꺼내는 것과 같죠.
</p>
<div className="warn">
<b>주의: 비밀번호는 복사하지 마세요</b> 클립보드 히스토리는 편리한 만큼,
복사한 <strong>비밀번호나 토큰도 목록에 남아요</strong>. 자리를 비운 사이 누가
<span className="kbd">Win</span>+<span className="kbd">V</span> 열면 보입니다.
민감한 값을 복사했다면 목록에서 바로 삭제하고, 애초에 비밀번호는
비밀번호 관리자의 자동 입력으로 다루는 안전해요.
</div>
<div className="tip">
<b>직접 확인해 보기</b> <span className="kbd">Win</span>+<span className="kbd">V</span> 눌러
히스토리를 켜고 페이지에서 서로 다른 문장 3개를 차례로
<span className="kbd">Ctrl</span>+<span className="kbd">C</span> 메모장을 열어
<span className="kbd">Win</span>+<span className="kbd">V</span> <strong>복사한 역순</strong>으로
붙여 보세요. 마지막으로 자주 쓰는 우리 Gitea 주소를 복사해 📌 고정까지!
</div>
</Section>
<Section n={6} title="Win + . 이모지 패널, 그리고 자동 치환 도구 한 입" sub="특수문자 곡예 은퇴 + 자주 치는 문장 자동화">
<p>
<span className="kbd">Win</span>+<span className="kbd">.</span>(마침표) 누르면
이모지 패널이 떠요. 커밋 메시지나 리뷰 코멘트에 👍 하나 넣으려고 이모지를
검색 사이트에서 복사해 오던 시절과 작별할 시간입니다.
</p>
<Code>{CODE_EMOJI}</Code>
<p>
걸음 <strong>자동 문자열 치환</strong> 도구는 "자주 치는 긴 문장"
짧은 약어로 등록해 두면, 약어를 치는 순간 자동으로 확장해 줘요. 문자를 보낼
"ㅇㅋ"라고 줄여 쓰는 컴퓨터가 거꾸로 주는 셈이죠 내가 약어를 치면
컴퓨터가 정식 문장으로 풀어 씁니다.
</p>
<Code>{CODE_AUTOHOTKEY}</Code>
<div className="tip">
<b>어디까지 자동화할까?</b> 이메일 주소, 자주 안내하는 링크, 매일 쓰는 인사말처럼
<strong> 글자 그대로 반복되는 </strong> 좋은 후보예요. 반대로 매번 내용이 조금씩
달라지는 문장은 자동화하면 오히려 어색한 복붙 티가 납니다.
</div>
</Section>
<Section n={7} title="실습 — 미션 두 개로 손에 새기기" sub="읽어서 아는 것 ≠ 손이 아는 것">
<p>
여기까지 읽었다면 머리는 알아요. 하지만 단축키는 <strong>손이 기억해야</strong>
진짜 그래서 미션 개를 준비했어요. 오늘부터 시작합니다.
</p>
<Code>{CODE_MISSION}</Code>
<Code>{CODE_BINGO}</Code>
<div className="olist">
<ol>
<li>미션 1(마우스 없이 전환) <strong>오늘</strong> 시작한다 벌점을 메모장에 정직하게 기록.</li>
<li>빙고판을 종이에 그리거나 인쇄해서 모니터 옆에 붙인다.</li>
<li>3 안에 빙고 3줄을 완성하면 멘토에게 자랑한다. (진짜로 자랑해도 됩니다)</li>
</ol>
</div>
<div className="tip">
<b>공부 </b> 단축키가 손에 붙는 데는 보통 <strong>3일의 어색함</strong>
필요해요. "마우스가 더 빠른데?" 싶은 3일을 버티는 코스의 전부입니다.
자전거 보조 바퀴를 떼던 날을 떠올려 보세요.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}> 여기까지 왔다면</h3>
<p className="muted">
이제 배치·텍스트 편집· 관리·클립보드까지, 마우스 없이 PC를 지휘하는
기본기를 갖췄어요. 손놀림은 앞으로 배울 모든 도구에서 그대로 통합니다
에디터, 터미널, 그리고 우리 Gitea까지. 다음은{' '}
<Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스에서
도구들이 달리는 길을 배우거나, 오늘 배운 단축키로{' '}
<Link to="/learn/coding"><strong>코딩 기초</strong></Link> 실습을 빠르게
진행해 보세요. 빙고 3 완성 인증은 멘토와의 주간 미팅에서!
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,382 @@
// : " PowerShell"
// Windows Terminal · , PowerShell , , , Git Bash
// 7 . ( · )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// · JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_THREE_SHELLS = `검은 창 삼형제 — 뭐가 뭔지 한 장 정리
이름 정체 언제 쓰나
cmd 윈도우의 원조 명령창 (1980년대 아주 오래된 배치(.bat) 파일
MS-DOS의 후손). 기능이 적음 실행할 정도. 요즘은 거의
PowerShell 마이크로소프트가 새로 만든 윈도우에서의 기본 선택지.
현대식 . 파란 배경이 상징 파일 관리, 프로그램 실행, 개발
Git Bash Git을 설치하면 딸려 오는 리눅스 명령을 그대로 쓰고 싶을 .
'리눅스 흉내' 강의·블로그의 리눅스 명령 따라 하기
결론: 평소엔 PowerShell, 리눅스 명령이 필요하면 Git Bash.
cmd는 "아, 옛날 거구나" 하고 알아만 두면 됩니다.`;
const CODE_WT_KEYS = `Windows Terminal 필수 단축키
동작 단축키
열기 Ctrl + Shift + T
이동 Ctrl + Tab
닫기 Ctrl + Shift + W
세로로 화면 분할 Alt + Shift + +
가로로 화면 분할 Alt + Shift + -
분할된 사이 이동 Alt + 방향키
글자 크게/작게 Ctrl + 마우스
분할 기능이 진짜 보물이에요. 왼쪽엔 서버 로그,
오른쪽엔 명령 입력 전환 없이 화면에서!`;
const CODE_PS_BASIC = `# PowerShell 기본 명령 — 폴더 산책하기
pwd # 지금 내가 있는 폴더 위치 (print working directory)
ls # 폴더 안에 뭐가 있는지 목록 (list)
cd frontend # frontend 폴더로 들어가기 (change directory)
cd .. # 단계 폴더로 나오기
cat README.md # 파일 내용을 터미널에 출력
mkdir test # test 폴더 만들기
clear # 화면 깨끗하게 지우기
# Tab 키를 활용하세요!
cd fro (Tab) cd frontend # 글자만 치고 Tab = 자동완성`;
const CODE_PS_VS_LINUX = `"어? 리눅스 명령이 되네?" — 반은 맞고 반은 함정
명령 리눅스에서 PowerShell에서
ls 진짜 ls 프로그램 Get-ChildItem의 별명(alias)
cd 내장 명령 Set-Location의 별명
cat 진짜 cat Get-Content의 별명
rm 파일 바로 삭제 Remove-Item의 별명 (동작 비슷)
rm -rf 폴더 폴더 통째 삭제 에러! -rf 옵션이 없음
PowerShell식: rm -r -fo 폴더 (Recurse + Force)
touch 파일 파일 생성 없음 New-Item 파일명
which 명령 명령 위치 찾기 없음 Get-Command 명령
핵심: PowerShell의 ls·cd·cat은 리눅스 명령을 '흉내 낸 별명'이에요.
간단한 통하지만, 옵션(-rf 같은 )부터는 서로 다른 언어입니다.
블로그의 리눅스 명령이 먹으면 Git Bash 탭에서 실행하세요.`;
const CODE_DAILY = `# 개발자가 매일 쓰는 명령 3종 세트
# 1) 네트워크 정보 IP 주소, 게이트웨이 확인
ipconfig
# 2) 지금 실행 중인 프로그램(프로세스) 전부 보기
tasklist
# 특정 프로그램만 골라 보기 (: node)
tasklist | findstr node
# 3) 응답 없는 프로그램 강제 종료
taskkill /f /im node.exe # 이름으로 (node.exe 전부 종료)
taskkill /f /pid 12345 # PID(프로세스 번호) 집어서`;
const CODE_PORT_KILL = `# 실전 단골 상황: "포트가 이미 사용 중입니다" 에러
# 개발 서버를 껐는데 유령처럼 남아서 포트를 잡고 있을 :
# 1단계 누가 5173 포트를 쓰는지 범인 찾기
netstat -ano | findstr :5173
# TCP 0.0.0.0:5173 ... LISTENING 23456 숫자가 PID!
# 2단계 PID가 어떤 프로그램인지 확인
tasklist | findstr 23456
# 3단계 종료
taskkill /f /pid 23456
# 3단계면 "포트 충돌" 에러의 90% 해결됩니다.`;
const CODE_ADMIN = `관리자 권한이 필요한 순간 vs 아닌 순간
일반 권한으로 충분 관리자 권한 필요
ls, cd, cat 파일 산책 프로그램 설치/제거 (winget 일부)
npm run dev 개발 서버 실행 hosts 파일 수정
git 명령 전부 방화벽 규칙 변경
폴더 파일 만들기/지우기 C:\\Program Files 안에 쓰기
서비스(Docker ) 시작/중지
관리자로 여는 :
시작 메뉴 "터미널" 검색 우클릭 "관리자 권한으로 실행"
( 제목에 '관리자:' 붙어 있으면 관리자 모드라는 표시)`;
const CODE_CUSTOM = `# 터미널 꾸미기 한 입 — 설정 파일 없이 되는 것부터
# Windows Terminal에서 Ctrl + , (쉼표) 설정 화면
# · 구성표: One Half Dark, Campbell 고르기만 하면
# · 배경 불투명도(아크릴): 살짝 비치는 배경, 은근 감성
# · 글꼴: 'Cascadia Code' (기본 내장, 코딩용 폰트)
# 걸음 (선택): 프롬프트를 예쁘게 oh-my-posh
winget install JanDeDobbeleer.OhMyPosh
# 설치 안내대로 프로필에 추가하면
# 현재 폴더·git 브랜치가 프롬프트에 컬러로 표시돼요.
# 주의: 꾸미기는 후식이에요. 명령이 먼저, 테마는 나중에!`;
const CODE_PRACTICE = `실습 순서 — Windows Terminal에 Git Bash 탭 추가하기
Git이 설치돼 있는지 확인 (PowerShell에서)
git --version # 버전이 나오면 OK, 없으면 git-scm.com에서 설치
Windows Terminal 열기 줄의 (아래 화살표) 클릭
목록에 "Git Bash" 보이면 클릭! (Git 설치 자동 등록됨)
보인다면: 설정 프로필 추가 프로필
이름: Git Bash
명령줄: C:\\Program Files\\Git\\bin\\bash.exe
저장 다시 열면 Git Bash가 나타나요.
Git Bash 탭에서 우리 프로젝트로 이동
cd /c/awesomedev/mirim-app # 경로 스타일이 다른 것에 주목!
ls
git log --oneline -5 # 최근 커밋 5 구경
관찰 포인트: PowerShell은 C:\\awesomedev\\mirim-app,
Git Bash는 /c/awesomedev/mirim-app 같은 폴더, 다른 표기법.
삼형제가 '다른 언어를 쓰는 형제'인지 몸으로 느끼는 순간!`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '검은 창 삼형제' },
{ n: 2, label: 'Windows Terminal' },
{ n: 3, label: 'PowerShell 기본기' },
{ n: 4, label: '매일 쓰는 명령' },
{ n: 5, label: '관리자 권한' },
{ n: 6, label: '꾸미기 한 입' },
{ n: 7, label: '실습: Git Bash 탭' },
];
export default function ToolTerminalPage() {
return (
<div>
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
<div className="hero">
<div className="eyebrow">Course · 부록 · 도구</div>
<h1>윈도우 터미널과 PowerShell</h1>
<p>
개발자의 하루는 검은 창에서 시작해서 검은 창에서 끝나요. 마우스 없이
키보드만으로 컴퓨터에게 말을 거는 cmd·PowerShell·Git Bash가 뭐가 다른지부터
Windows Terminal로 개를 창에 모으는 실습까지 함께 갑니다.
</p>
<div className="chip-row">
<span className="chip">예상 소요 40</span>
<span className="chip">실습 2</span>
<span className="chip">준비물: 윈도우 PC</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="검은 창 삼형제 — cmd, PowerShell, Git Bash" sub="셋 다 '터미널'이라 부르지만 쓰는 언어가 달라요">
<p>
윈도우에서 "터미널 열어 보세요" 하면 사람마다 다른 창이 열려요. 셋은
<strong> 같은 컴퓨터에게 말을 거는 가지 통역사</strong>라고 생각하면 됩니다.
컴퓨터라는 외국인은 같지만, 어떤 통역사를 쓰느냐에 따라 통하는 말이 달라요.
</p>
<Code>{CODE_THREE_SHELLS}</Code>
<p>
여기서 용어 하나만 정리해요. <strong>터미널</strong> 글자가 표시되는
<strong> (화면)</strong>이고, <strong>(shell)</strong> 안에서 명령을
해석해 주는 <strong>통역 프로그램</strong>이에요. 카페(터미널) 안에 바리스타()
있는 구조 그래서 하나의 Windows Terminal 안에 PowerShell 탭과 Git Bash 탭을
동시에 있는 거예요. 이건 섹션 7에서 직접 해봅니다.
</p>
<div className="tip">
<b>어썸데브에서는?</b> 우리 스택의 Docker·AWS EC2 서버는 리눅스라서, 서버에
접속하면 리눅스 명령을 쓰게 돼요. 그래서 Git Bash로 리눅스 명령에 미리 익숙해져
두면 나중에 서버 다룰 훨씬 편합니다.
</div>
</Section>
<Section n={2} title="Windows Terminal 설치와 탭·분할" sub="삼형제를 한 창에 모으는 최신 터미널">
<p>
예전엔 cmd 따로, PowerShell 따로 열어서 작업표시줄이 검은 창으로 가득했어요.
<strong> Windows Terminal</strong> 창들을 <strong>브라우저 탭처럼</strong> 창에
모아 주는 마이크로소프트의 공식 터미널 앱이에요. 크롬에서 여러 쓰듯이,
탭마다 다른 셸을 띄울 있죠.
</p>
<div className="olist">
<ol>
<li>윈도우 11이라면 이미 기본 설치돼 있어요 시작 메뉴에서 <strong>"터미널"</strong> 검색.</li>
<li>없다면 <strong>Microsoft Store</strong>에서 "Windows Terminal" 검색 설치. !</li>
<li>열면 기본으로 PowerShell 탭이 하나 떠요. 줄의 <strong></strong> 버튼을 누르면 다른 목록이 나옵니다.</li>
</ol>
</div>
<Code>{CODE_WT_KEYS}</Code>
<div className="tip">
<b>직접 확인해 보기</b> Windows Terminal을 열고 <span className="kbd">Alt</span> +
<span className="kbd">Shift</span> + <span className="kbd">+</span> 화면을 세로로
쪼개 보세요. 왼쪽 창에서 <span className="icode">ls</span>, 오른쪽 창에서도{' '}
<span className="icode">ls</span> 개의 독립된 셸이 화면에 사는 확인!
프론트 개발 서버(<span className="icode">npm run dev</span>) 한쪽에 두고
다른 쪽에서 git 명령을 치는 실무의 기본 자세예요.
</div>
</Section>
<Section n={3} title="PowerShell 기본기" sub="ls·cd는 되는데 rm -rf는 안 된다?">
<p>
터미널에서 가장 먼저 배울 <strong>폴더 산책</strong>이에요. 탐색기에서
더블클릭으로 폴더를 드나들던 , 이제 글자로 합니다. "내가 지금 어디 있지?"
(<span className="icode">pwd</span>) "여기 뭐가 있지?"(<span className="icode">ls</span>)
"저 방으로 들어가자"(<span className="icode">cd</span>) 개면 어디든 있어요.
</p>
<Code>{CODE_PS_BASIC}</Code>
<p>
그런데 여기 <strong>중요한 함정</strong> 있어요. PowerShell에서{' '}
<span className="icode">ls</span> 되니까 "리눅스랑 같네?" 하고 안심하면,
어느 블로그에서 복사한 <span className="icode">rm -rf</span> 에러를 뿜습니다.
PowerShell의 리눅스풍 명령은 <strong>진짜가 아니라 별명(alias)</strong>이거든요
외국어 마디 흉내 내는 것과 언어를 하는 다르죠.
</p>
<Code>{CODE_PS_VS_LINUX}</Code>
<div className="warn">
<b>삭제 명령은 항상 손을 멈추고 </b> <span className="icode">rm -r -fo</span>
휴지통을 거치지 않고 <strong>즉시, 영구히</strong> 지웁니다. 되돌리기(Ctrl+Z) 없어요.
엔터 치기 전에 <span className="icode">pwd</span> 위치, 지우려는 이름의 오타를
확인하는 습관을 들이세요.
</div>
</Section>
<Section n={4} title="매일 쓰는 명령 — ipconfig, tasklist, 프로세스 죽이기" sub="개발하다 막히면 열에 아홉은 이 셋으로 해결">
<p>
이번엔 폴더 산책을 넘어, 컴퓨터의 <strong>상태를 들여다보는</strong> 명령들이에요.
작업 관리자(<span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd">Esc</span>)
하던 일을 글자로 하는 셈이죠. 특히 <strong>프로세스 강제 종료</strong> 개발 서버가
유령처럼 남아 포트를 점거할 매일같이 쓰게 됩니다.
</p>
<Code>{CODE_DAILY}</Code>
<p>
<span className="icode">|</span> (파이프) 기호는 <strong> 명령의 출력을 명령에게
넘겨주는 배관</strong>이에요. <span className="icode">tasklist | findstr node</span>
"전체 목록을 뽑아서(tasklist), 그중 node가 들어간 줄만 걸러 줘(findstr)"라는
수백 줄에서 눈으로 찾는 대신 컴퓨터에게 시키는 거죠.
</p>
<Code>{CODE_PORT_KILL}</Code>
<div className="tip">
<b>직접 확인해 보기</b> 지금 터미널을 열고 <span className="icode">tasklist | findstr chrome</span>
실행해 보세요. 크롬이 프로세스 여러 개로 나뉘어 도는 보일 거예요(탭마다 별도 프로세스!).
그리고 <span className="icode">netstat -ano | findstr LISTENING</span>으로 PC에서
지금 열려 있는 포트들도 구경해 보세요. 네트워크 코스에서 배운 '포트 = 방 번호'
실제로 목록으로 보입니다.
</div>
</Section>
<Section n={5} title="관리자 권한으로 실행이 필요한 순간" sub="집 열쇠와 마스터키는 다르다">
<p>
윈도우는 평소에 여러분을 <strong>'내 방 열쇠' 가진 사용자</strong> 대해요.
폴더 안에서는 뭐든 있지만, 건물 전체 설비(시스템 폴더, 방화벽, 서비스)
만지려면 <strong>마스터키 = 관리자 권한</strong> 필요합니다. 실수로 시스템을
망가뜨리지 못하게 하는 안전장치예요.
</p>
<Code>{CODE_ADMIN}</Code>
<p>
에러 메시지에 <strong>"액세스가 거부되었습니다"</strong>{' '}
<strong>"권한이 없습니다(permission denied)"</strong> 보이면 관리자 권한을 의심해
보세요. 반대로, 평소 작업까지 습관적으로 관리자 창에서 하는 금물 마스터키를
꽂아 돌아다니는 것과 같아서, 오타 번의 사고가 시스템 전체로 번질 있어요.
</p>
<div className="warn">
<b>회사 규칙</b> 어썸데브 지급 PC에서 시스템 설정을 바꾸는 관리자 작업(방화벽, hosts
수정 ) 하기 전에 멘토에게 먼저 물어보세요. "왜 필요한지" 설명할 있어야
하는 작업들입니다.
</div>
</Section>
<Section n={6} title="터미널 꾸미기 한 입" sub="매일 보는 창이니까, 보기 좋게">
<p>
터미널은 하루 종일 들여다보는 작업 공간이에요. 책상 정리하듯 색과 글꼴을 정돈해
두면 눈도 피곤하고, 무엇보다 <strong>프롬프트에 정보를 띄워 두면 실수가 줄어요</strong>
지금 어느 폴더, 어느 git 브랜치에 있는지 매번 명령으로 확인하지 않아도 되니까요.
</p>
<Code>{CODE_CUSTOM}</Code>
<div className="tip">
<b>추천 조합</b> 구성표 <strong>One Half Dark</strong> + 글꼴{' '}
<strong>Cascadia Code</strong> + 불투명도 90%. 설정 화면에서 클릭 번이면 끝나고,
어느 PC에서나 같은 조합을 있어요. oh-my-posh는 여유 있을 도전
없어도 개발에는 전혀 지장 없습니다.
</div>
</Section>
<Section n={7} title="실습: Git Bash 탭 추가하고 우리 프로젝트로 이동" sub="삼형제를 한 창에 — 이 코스의 최종 미션">
<p>
이제 배운 전부 모아 봅니다. Windows Terminal 안에 <strong>Git Bash </strong>
추가하고, 리눅스식 명령으로 우리 프로젝트 폴더까지 걸어 들어가는 실습이에요.
섹션 1 "터미널은 카페, 셸은 바리스타" 비유가 실제로 어떤 모습인지 확인하는 시간!
</p>
<Code>{CODE_PRACTICE}</Code>
<p>
④번에서 경로가 <span className="icode">/c/awesomedev/...</span> 바뀐 보이나요?
Git Bash는 리눅스 흉내를 내느라 <strong>드라이브 문자(C:) /c 폴더처럼</strong> 표현해요.
역슬래시(<span className="icode">\</span>) 대신 슬래시(<span className="icode">/</span>)
쓰는 것도 리눅스식이죠. 나중에 EC2 서버에 접속하면 진짜 리눅스가 모습 그대로
여러분을 맞이합니다.
</p>
<div className="tip">
<b> 걸음 </b> Git Bash 탭에서 <span className="icode">git remote -v</span> 실행해
보세요. 우리 프로젝트가 회사 Gitea(<span className="icode">edu.awesomedevapp.com:3000</span>)
연결돼 있는 보일 거예요. 화면을 분할해서 한쪽은 PowerShell, 한쪽은 Git Bash로
같은 폴더에서 <span className="icode">ls</span> 보는 것도 재밌습니다
출력 모양이 미묘하게 달라요.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}> 여기까지 왔다면</h3>
<p className="muted">
이제 검은 창이 무섭지 않죠? cmd·PowerShell·Git Bash를 구분할 있고,
Windows Terminal 창에서 탭과 분할로 오가며, 포트를 점거한 유령 프로세스도
직접 잡을 있게 됐어요. 터미널은 <strong>모든 개발 도구의 현관문</strong>
다음은 문으로 들어가 쓰는 번째 도구,{' '}
<Link to="/learn/git"><strong>Git과 버전 관리</strong></Link> 코스로 이어 가세요.
방금 만든 Git Bash 탭이 바로 교실입니다.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,358 @@
// : "VPN " (VPN) ()
// ' ' 7 .
// (VPN= VPN = vs
// VPN VPN : Caddy )
// : API ' ' . global.css
// (step-card, code-block, tip, warn ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_VPN_TUNNEL = `VPN 없이 보낼 때 vs VPN 터널로 보낼 때
일반 인터넷 (엽서):
[ PC] "안녕! 비번은 1234야" [중간 장비들이 읽을 있음] [목적지]
VPN (밀봉된 강철 파이프):
[ PC] 암호화된 터널 [VPN 서버] [목적지]
중간에서 훔쳐봐도 (암호문) 보임
핵심 가지:
1) 암호화 내용물을 아무도 읽게 잠금
2) 터널링 원래 패킷을 통째로 봉투에 넣어 배송
3) 출구 변경 목적지 입장에선 'VPN 서버' 보낸 것처럼 보임`;
const CODE_COMPANY_VPN = `회사 VPN의 그림 — "회사 건물로 순간이동하는 문"
(·카페 어디서든) (회사 내부망)
[재택근무자 노트북] VPN 터널 [회사 VPN 게이트웨이]
[사내 Gitea] [내부 DB] [관리자 페이지]
외부 차단 외부 차단 외부 차단
내부망 서비스들은 바깥 인터넷에서 아예 접속이 되게 막아두고,
VPN 터널을 통과한 사람에게만 문을 열어줍니다.
"회사 출입증을 찍고 들어온 사람" 사무실 자료를 있는 것과 같아요.`;
const CODE_PROXY_AGENT = `프록시(Proxy) = 대리인, 심부름꾼
대신 요청을 전달하고, 답장도 대신 받아다 주는 중간 서버예요.
[클라이언트] "이거 부탁해"> [프록시] "(내가 대신) 주세요"> [서버]
[클라이언트] <"여기 받아" [프록시] <"여기요" [서버]
서버 입장에서 상대는 '프록시'예요. 진짜 요청자가 누군지는
프록시가 알려주지 않는 모릅니다. 연예인의 매니저처럼,
모든 연락이 매니저(프록시) 거쳐 가는 구조죠.`;
const CODE_FWD_VS_REV = `정방향(Forward) 프록시 vs 리버스(Reverse) 프록시
똑같은 '대리인'인데, 누구 편에 있느냐가 달라요.
정방향 프록시: 클라이언트 편의 대리인 (: 학교 인터넷 필터)
[학생 PC들] > [학교 프록시] > 인터넷
"이 사이트는 차단!" / 자주 가는 페이지 캐시
서버는 '학교 프록시' 보임 클라이언트가 숨겨짐
리버스 프록시: 서버 편의 대리인 (: 우리 Caddy!)
인터넷 > [Caddy] > [Spring Boot 백엔드]
HTTPS 처리 / 요청을 알맞은 서비스로 분배
클라이언트는 'Caddy' 보임 서버 내부가 숨겨짐
정리: 정방향은 "요청 보내는 쪽" 대신하고,
리버스는 "요청 받는 쪽" 대신합니다.`;
const CODE_PUBLIC_WIFI = `공용 와이파이에서 무슨 일이 생길 수 있나
[ ] ~~무선 전파~~ [카페 공유기?] 인터넷
전파는 공중에 뿌려집니다. 같은 와이파이에 붙은
누군가가 '수신' 있어요. 심지어 이름만 그럴듯한
가짜 AP("Free_Cafe_WiFi") 세워두는 경우도 있고요.
그나마 다행: 요즘 웹은 대부분 HTTPS라 내용은 암호화돼요.
그래도 새는 : 어떤 사이트에 접속하는지(도메인), HTTPS가 아닌
·사이트의 통신, DNS 질문 내용 .
VPN을 켜면: 폰에서 나가는 모든 통신이 터널 안으로 들어가
같은 와이파이의 누구도 내용은 물론 행선지조차 봅니다.`;
const CODE_OUR_FLOW = `우리 학습 플랫폼의 요청 여행 — 리버스 프록시 실물 관찰
브라우저: https://edu.awesomedevapp.com/api/...
(DNS로 IP를 찾고, 443 포트로 접속 네트워크 코스 복습!)
AWS EC2 서버 도착 제일 먼저 맞이하는 Caddy
Caddy가 하는 :
- HTTPS 인증서 처리 (자물쇠 아이콘의 주인공)
- 경로를 보고 교통정리: /api/* 백엔드로
Spring Boot 백엔드 (Docker 컨테이너)
로직 처리, 필요하면
PostgreSQL 에서 데이터 조회
응답이 같은 길을 거꾸로: 백엔드 Caddy 브라우저
여러분이 페이지를 여는 순간에도 여행이 벌어졌어요.
브라우저는 끝까지 Caddy하고만 대화했고, 백엔드와 DB는
바깥에서 보이지 않는 무대 (backstage) 있습니다.`;
const CODE_DEVTOOLS_CHECK = `# 실습 A — 개발자 도구로 '대리인' 흔적 찾기
1) 페이지에서 F12 Network F5 새로고침
2) 아무 요청이나 클릭 Headers 확인
- Remote Address: 서버IP:443 여러분이 실제로 접속한 = Caddy!
- Response Headers에서 Server: Caddy 찾아보세요.
(응답을 '누가' 돌려줬는지 서버가 스스로 밝히는 칸이에요)
3) 백엔드(Spring Boot) IP나 포트는 어디에도 보이죠?
그게 바로 리버스 프록시가 서버 내부를 숨긴다는 증거입니다.
# 실습 B 우리 Gitea도 같은 구조인지 확인
1) edu.awesomedevapp.com:3000 (Gitea) 접속 F12 Network 새로고침
2) 요청의 Remote Address와 응답 헤더를 위와 비교해 보세요.
3) 관찰한 내용을 정리해 멘토에게 설명해 보기:
"우리 서비스에서 프록시를 거치는 것과 안 거치는 것은?"`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: 'VPN = 터널' },
{ n: 2, label: '회사와 VPN' },
{ n: 3, label: '프록시 = 대리인' },
{ n: 4, label: '정방향 vs 리버스' },
{ n: 5, label: '공용 와이파이' },
{ n: 6, label: '무료 VPN의 함정' },
{ n: 7, label: '실습: Caddy 흐름' },
];
export default function VpnProxyPage() {
return (
<div>
{/* 히어로: 이 코스가 다루는 두 가지 '중간 통로' */}
<div className="hero">
<div className="eyebrow">Course · 네트워크</div>
<h1>VPN과 프록시<br /> 터널과 대리인, 가지 중간 통로</h1>
<p>
데이터가 목적지까지 <strong>어떤 통로로, 누구를 거쳐</strong> 가는지가 이번 코스의
주제예요. 멀리 필요 없이 우리 플랫폼의 <strong>Caddy</strong> 살아 있는 교재입니다
여러분은 이미 매일 리버스 프록시를 통과하고 있었거든요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">실습 2</span>
<span className="chip">선수 코스: 네트워크의 이해</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="VPN = 암호화된 전용 터널" sub="엽서로 보내던 편지를 강철 파이프로 보내기">
<p>
일반 인터넷 통신은 <strong>엽서</strong> 비슷해요. 내용이 겉에 적혀 있어서,
배달 경로에 있는 우체국(중간 네트워크 장비)마다 마음만 먹으면 읽을 있죠.
<strong> VPN</strong>(Virtual Private Network, 가상 사설망) 엽서를
<strong> 아무도 여는 밀봉 상자에 넣고, 나와 VPN 서버 사이에 전용 파이프</strong>
뚫어 보내는 기술이에요. 그래서 항상 <strong>"터널"</strong>이라고 부릅니다.
</p>
<Code>{CODE_VPN_TUNNEL}</Code>
<p>
이름을 뜯어 보면 정체가 보여요. 진짜 전용선(Private Network) 까는 대신,
모두가 쓰는 인터넷 위에 <strong>가상(Virtual)</strong> 전용 통로를 만든 거예요.
지하철 노선을 새로 파는 대신, 기존 도로 위에 <strong>버스 전용차로</strong>
그어 놓은 것과 비슷하죠.
</p>
<div className="tip">
<b>미리 잡기</b> VPN을 켜면 "내 위치가 바뀐 것처럼 보인다" 얘기 들어봤죠?
그림의 <strong>출구 변경</strong> 때문이에요 목적지 서버는 터널의 출구인
<strong> VPN 서버의 IP</strong> 봅니다. IP는 터널 입구 뒤에 숨어 있고요.
</div>
</Section>
<Section n={2} title="회사가 VPN을 쓰는 이유" sub="원격근무자를 위한 '순간이동 출입문'">
<p>
회사에는 바깥에 절대 공개하면 되는 것들이 있어요. 소스 코드 저장소, 내부 DB,
관리자 페이지 같은 것들이죠. 가장 안전한 방법은 <strong>아예 인터넷에서 접속
하게 막아버리는 </strong>인데, 그러면 재택근무자나 출장 직원은 일을 해요.
딜레마를 푸는 열쇠가 회사 VPN입니다.
</p>
<Code>{CODE_COMPANY_VPN}</Code>
<p>정리하면, 회사 VPN이 주는 일은 이렇습니다.</p>
<ol className="olist">
<li>
<strong>내부망 접근</strong> 터널을 통과하면 노트북이 <strong>회사 건물 안에
앉아 있는 것처럼</strong> 내부 서비스에 접속할 있어요. 몸은 집인데 네트워크는 출근한 상태죠.
</li>
<li>
<strong>출입 통제</strong> 터널에 들어오려면 인증(계정·인증서) 필요해요.
"누가 내부망에 들어왔는지" 회사가 정확히 알고 관리할 있습니다.
</li>
<li>
<strong>구간 암호화</strong> 집과 회사 사이 구간이 통째로 암호화돼서,
카페에서 접속해도 회사 데이터가 새지 않아요.
</li>
</ol>
<div className="tip">
<b>우리 회사 맥락</b> AWESOMEDEV의 서버들도 같은 철학으로 운영돼요
<strong> 바깥에 열어둘 문은 최소한으로(웹은 Caddy 하나), 나머지는 잠근다.</strong>
예를 들어 PostgreSQL은 인터넷에 직접 노출하지 않고 서버 내부에서만 접속하게 합니다.
"DB 포트를 전 세계에 열어둔 서버" 실제 해킹 사고의 단골 원인이에요.
</div>
</Section>
<Section n={3} title="프록시 = 대리인" sub="그리고 우리 Caddy가 바로 그 실물이에요">
<p>
<strong>프록시(Proxy)</strong> 영어로 <strong>"대리인"</strong>이라는 뜻이에요.
내가 서버와 직접 대화하는 대신, <strong>중간에서 요청과 응답을 대신 전달해 주는
서버</strong> 말합니다. 연예인에게 직접 연락할 없고 모든 연락이 매니저를
거치는 것처럼요.
</p>
<Code>{CODE_PROXY_AGENT}</Code>
<p>대리인을 중간에 세우면 좋은 점이 생겨요.</p>
<ol className="olist">
<li><strong>숨기기</strong> 한쪽의 정체(IP, 내부 구조) 상대에게 감출 있어요.</li>
<li><strong>거르기</strong> 이상한 요청을 대리인 선에서 차단할 있어요.</li>
<li><strong>기억하기(캐시)</strong> 자주 오가는 답을 대리인이 외워 뒀다가 바로 답해줄 있어요.</li>
<li><strong>나눠주기</strong> 요청을 여러 서버에 분배(로드 밸런싱) 있어요.</li>
</ol>
<p>
그리고 이건 교과서 개념이 아니에요. 여러분이 지금 보고 있는 페이지도
<strong> Caddy라는 프록시</strong> 거쳐서 왔습니다. 어느 방향의 대리인인지는
다음 섹션에서 갈라 볼게요.
</p>
</Section>
<Section n={4} title="정방향 vs 리버스 프록시" sub="같은 대리인, 다른 소속">
<p>
프록시는 <strong>"누구 편에 서 있느냐"</strong> 종류로 나뉘어요.
클라이언트(요청 보내는 ) 대리인이면 <strong>정방향(forward)</strong>,
서버(요청 받는 ) 대리인이면 <strong>리버스(reverse)</strong>입니다.
그림으로 보면 화살표 구조는 똑같은데, <strong>숨겨지는 쪽이 반대</strong>예요.
</p>
<Code>{CODE_FWD_VS_REV}</Code>
<p>
정방향 프록시의 대표 예가 <strong>학교·회사의 인터넷 필터</strong>예요. 학생 PC들의
모든 요청이 프록시를 거치니까, 거기서 유해 사이트를 차단하거나 트래픽을 기록할
있죠. 리버스 프록시의 대표 예가 바로 <strong>우리 Caddy</strong>고요 인터넷에서
요청을 현관에서 받아 HTTPS를 처리하고, Spring Boot 백엔드로 안내합니다.
백엔드와 DB는 손님 눈에 보이는 주방인 셈이에요.
</p>
<div className="warn">
<b>헷갈림 주의</b> 참고로 VPN과 프록시는 사촌이지 쌍둥이가 아니에요.
프록시는 보통 <strong>특정 프로그램의 요청만</strong> 대신 전달하고 암호화를 보장하지
않지만, VPN은 <strong>기기에서 나가는 통신 전체</strong> 암호화 터널에 넣습니다.
"IP를 숨겨준다" 점이 닮아서 자주 섞여 불리는 것뿐이에요.
</div>
</Section>
<Section n={5} title="공용 와이파이에서 VPN의 가치" sub="전파는 공중에 뿌려진다는 사실">
<p>
카페·지하철의 공용 와이파이는 편하지만, 무선 전파는 <strong>공중에 방송</strong>되는
거라 같은 와이파이에 붙은 다른 사람이 엿들을 여지가 있어요. 교실에서 쪽지를 돌리는
아니라 <strong>칠판에 써서 보여주는 </strong> 가깝달까요.
</p>
<Code>{CODE_PUBLIC_WIFI}</Code>
<p>
다행히 요즘 웹은 대부분 HTTPS라서 <strong>내용</strong> 암호화돼요. 하지만
"어느 사이트에 접속하는지" 같은 <strong>행적</strong> 여전히 보일 있고,
이름만 그럴듯한 <strong>가짜 와이파이</strong> 속아 붙는 위험도 있죠.
VPN을 켜면 기기에서 나가는 통신 전체가 터널로 들어가서, 같은 와이파이의 누구도
내용은 물론 행선지조차 없게 됩니다.
</p>
<div className="tip">
<b>실무 습관</b> 공용 와이파이에서 회사 (코드 푸시, 관리자 페이지 접속) 해야
한다면 VPN을 먼저 켜는 기본기예요. 회사 자료는 "내 것" 아니라 "맡은 것"이니까,
단계 조심하는 습관을 들여 두세요.
</div>
</Section>
<Section n={6} title="무료 VPN의 함정" sub="공짜라면… 내가 상품일 수 있어요">
<p>
"VPN이 좋다니 무료 VPN 앱을 깔아야지!" 잠깐만요. VPN의 구조를 다시 떠올려 보세요.
<strong> 모든 통신이 VPN 서버를 통과</strong>합니다. VPN 업체는 마음만 먹으면
인터넷 사용 전체를 들여다볼 있는 자리에 앉아 있어요. 그래서
<strong> "그 터널의 주인을 믿을 수 있는가"</strong> VPN 선택의 전부입니다.
</p>
<div className="warn">
<b>무료 VPN이 위험할 있는 이유</b> 서버 운영엔 돈이 드는데 사용자에게 받는다면,
돈은 어디서 나올까요? 실제로 문제가 사례들이 있어요
이용 기록을 수집해 광고 업체에 파는 경우, 통신에 광고를 끼워 넣는 경우,
심하면 사용자의 회선을 다른 고객에게 <strong>출구로 되파는</strong> 경우까지요.
도청을 피하려고 들어간 터널의 주인이 도청자라면 최악의 교환이죠.
</div>
<p>
그래서 판단 기준은 이렇게 잡으면 돼요: <strong>돈의 흐름이 투명한가</strong>(유료
구독, 신뢰할 만한 운영사), <strong>기록을 남기지 않는다는 정책(no-log) 외부 감사로
증명했는가</strong>, 그리고 회사 일이라면 <strong>회사가 지정한 VPN만</strong> .
참고로 회사 VPN은 함정과 무관해요 터널의 주인이 우리 회사 자신이니까요.
</p>
</Section>
<Section n={7} title="실습 — 우리 플랫폼의 프록시 흐름 복습" sub="매일 지나던 길을 눈으로 확인하기">
<p>
이제 우리 플랫폼에서 리버스 프록시를 <strong>실물로</strong> 관찰해 시간이에요.
여러분의 요청 하나가 AWS EC2 서버에 도착해서 응답이 되어 돌아오기까지,
등장인물은 이렇습니다.
</p>
<Code>{CODE_OUR_FLOW}</Code>
<div className="tip">
<b>직접 확인해 보기 </b> 지금 페이지에서 <span className="kbd">F12</span>
<span className="kbd">Network</span> <span className="kbd">F5</span> 새로고침.
아무 요청이나 눌러 응답 헤더에서 <span className="icode">Server: Caddy</span>
찾아보세요. "이 응답, 대리인이 전달했습니다"라는 서명이에요. 그리고 백엔드의
주소가 <strong>어디에도 보인다</strong> 것까지 확인하면 완벽!
</div>
<div className="tip">
<b>직접 확인해 보기 </b> 우리 Gitea(<span className="icode">edu.awesomedevapp.com:3000</span>)에도
같은 방법을 적용해 비교해 보세요. Remote Address와 응답 헤더가 학습 플랫폼과
어떻게 다른가요? 관찰 결과를 바탕으로 "우리 인프라에서 프록시를 거치는 경로와
아닌 경로" 그림 장으로 그려 멘토에게 설명해 보면 최고의 복습이 됩니다.
</div>
<Code>{CODE_DEVTOOLS_CHECK}</Code>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>🛡 여기까지 왔다면</h3>
<p className="muted">
이제 <strong>터널(VPN)</strong> <strong>대리인(프록시)</strong>, 가지 중간
통로를 구분할 있고, 우리 Caddy가 리버스 프록시라는 것도 눈으로 확인했어요.
데이터가 다니는 자체가 궁금해졌다면{' '}
<Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스로 기초를
다지고, "터널 속에서 오가는 암호화는 어떻게 동작할까?" 궁금해졌다면 다음
보안 코스에서 HTTPS와 인증서를 이어서 배워 보세요. 실습 ②에서 그린 그림은
멘토 리뷰를 받아 과제로 제출하는 , 잊지 말고요!
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,360 @@
// : " " ' '
// 8 .
// ( 3 )
// : API ' ' . global.css
// (step-card, code-block, tip ) .
// JSX / .
import { Link } from 'react-router-dom';
// ·
const CODE_BLUEPRINT = `같은 '집'을 표현하는 두 가지 그림
건축 도면 (와이어프레임) 완성 조감도 (디자인 시안)
··창문의 위치와 크기 벽지 , 조명 분위기, 가구 브랜드
선과 네모, 글자뿐 사진처럼 예쁜 렌더링
그리는 10 그리는 며칠
"동선이 이상한데?" 즉시 수정 고치려면 처음부터 다시
순서가 핵심이에요. 도면에서 구조를 확정한 뒤에
꾸미기 시작해야, 지어 놓고 벽을 부수는 일이 없어요.`;
const CODE_LOFI = `완성도를 일부러 낮추는 이유 — '버리는 비용' 비교
손그림 와이어프레임 1: 10 버려도 10 손해
피그마 예쁜 시안 1: 3시간 버리면 3시간 + 아까운 마음
코드로 만든 화면 1: 이틀 버리자는 말을 꺼내기도 힘듦
아이디어 10 좋은 보통 1~2.
나머지 8개를 '싸게' 버릴 있어야 좋은 빨리 찾습니다.
+ 심리 효과: 대충 그린 그림엔 "여기 이상해요"라고 말하기 쉽지만,
반짝반짝한 시안엔 "색이 예쁘네요" 같은 겉모습 얘기만 나와요.`;
const CODE_3STEPS = `와이어프레임의 3단계 — 점점 또렷해지는 사진처럼
1단계 손그림 (Lo-Fi)
종이+. 네모와 낙서 수준. 1장에 1~2.
목적: 아이디어 쏟아내고 골라내기
2단계 회색 박스 (Mid-Fi)
피그마 도구로. 회색 네모 + 진짜 텍스트 + 정확한 크기.
·사진·아이콘은 아직 금지!
목적: 배치와 흐름을 팀과 확정하기
3단계 디자인 시안 (Hi-Fi)
·폰트·이미지·로고까지 입힌 '완성 예상도'.
목적: 개발 최종 확인, 개발자에게 전달
함정: 1단계를 건너뛰고 3단계부터 시작하면
'예쁘지만 구조가 이상한' 화면이 나옵니다.`;
const CODE_VOCAB = `화면 부품 어휘장 — 팀 대화가 빨라지는 단어들
헤더(Header) 화면 고정 . 로고·메뉴·프로필이 사는
네비게이션(Nav) 페이지 사이를 이동하는 메뉴 묶음 (상단 , 사이드바)
카드(Card) 정보 덩어리를 담는 네모. 목록에서 반복됨
모달(Modal) 화면 위에 '팝' 뜨는 . 배경은 어두워짐
(Tab) 같은 자리에서 내용만 갈아 끼우는 전환 메뉴
버튼(Button) 누르면 무언가 '일어나는' (이동만 하면 링크)
(Form) 입력창 + 제출 버튼 묶음. 로그인 화면이 대표
푸터(Footer) 아래. 회사 정보·약관 링크
"위에 그거, 누르면 뜨는 네모"라고 말하면 회의가 3 길어져요.
"헤더의 프로필 버튼을 누르면 모달이 떠요" 문장 !`;
const CODE_FLOW = `화면 플로우 — 화면 '한 장'이 아니라 '이동'을 그린다
[로그인] 성공> [코스 목록] 카드 클릭> [코스 상세]
실패 검색
v v
[에러 메시지 [검색 결과]
+ 다시 입력]
화살표 위에 '무엇을 하면 이동하는지'(조건) 적으세요.
플로우를 그리다 보면 이런 질문이 저절로 나옵니다:
- 로그인에 실패하면? (에러 화면이 설계에 있나?)
- 검색 결과가 0개면? ( 화면은 보여 주지?)
코드를 짜기 '전에' 구멍을 찾는 플로우의 힘이에요.`;
const CODE_REVERSE = `역설계 실습 — 지금 보고 있는 이 플랫폼을 도면으로 되돌리기
준비물: 종이 1, 1자루, 학습 플랫폼
1) 학습 센터 메인 화면을 띄운다
2) 눈에 보이는 전부 '네모'로만 종이에 옮긴다
- 이미지는 X자 네모
- 글자는 구불구불한 (진짜 글자 쓰지 말기!)
- 버튼은 모서리 둥근 네모
3) 네모 옆에 섹션 4에서 배운 어휘로 이름표를 단다
(헤더? 카드? 네비게이션?)
4) 사람 것과 비교 같은 화면인데 네모 크기·개수가
다르다면, 서로 '중요하다고 본 것' 달랐다는 !
목표 시간: 15. 그리기 대회가 아니에요 구조를 '읽어내는' 연습입니다.`;
const CODE_FIGMA_KIT = `피그마 와이어프레임 키트 만들기 — 나만의 레고 블록 상자
1) figma.com 무료 가입 파일 (Design file)
2) 프레임 만들기: F 오른쪽에서 Desktop(1440) 선택
3) 부품 5개를 회색으로만 만든다:
- 헤더: 가로로 네모 (회색 #DDD 같은 밝은 회색)
- 카드: 세로 네모 + 위쪽에 X자 이미지 자리
- 버튼: 모서리 둥근 작은 네모 + 가운데 텍스트
- 입력창: 테두리만 있는 네모 + 왼쪽 안내 텍스트
- : 글자 3 나란히 + 선택된 아래만 밑줄
4) 부품을 선택 우클릭 Create component ( 표시)
5) 왼쪽 Assets 탭에서 부품을 끌어다 화면 조립!
컴포넌트로 만들어 두면 React와 똑같은 사고방식이에요
부품 하나를 고치면 복사해 모든 곳이 함께 바뀝니다.`;
const CODE_CHECK = `와이어프레임 셀프 체크 — 넘어가기 전에 3가지만
색을 썼나요? 회색 말고 색이 보이면 아직 단계가 아님
화면이 '한 장'뿐인가요? 성공/실패/ 화면까지 그렸는지
말로 설명이 되나요? "여기는 헤더, 누르면 모달이..."
부품 이름으로 화면 전체를 설명할 있으면 통과!`;
//
// : . props .
function Section({ n, title, sub, children }) {
return (
<section className="step-card" id={`sec-${n}`}>
<div className="step-head">
<span className="step-num">{n}</span>
<div>
<h3>{title}</h3>
{sub && <span className="step-sub">{sub}</span>}
</div>
</div>
<div className="step-body">{children}</div>
</section>
);
}
function Code({ children }) {
return <pre className="code-block"><code>{children}</code></pre>;
}
const SECTIONS = [
{ n: 1, label: '와이어프레임 = 도면' },
{ n: 2, label: '왜 대충 그리나' },
{ n: 3, label: '3단계 진화' },
{ n: 4, label: '화면 부품 어휘' },
{ n: 5, label: '플로우 그리기' },
{ n: 6, label: '역설계 실습' },
{ n: 7, label: '피그마 키트' },
{ n: 8, label: '7주차 프로젝트로' },
];
export default function WireframePage() {
return (
<div>
{/* 히어로: 이 코스가 무엇을 다루고 어디로 이어지는지 */}
<div className="hero">
<div className="eyebrow">Course · 디자인</div>
<h1>와이어프레임 기초<br /> 코드보다 먼저 그리는 설계 도면</h1>
<p>
집을 지을 도면 없이 벽돌부터 쌓는 사람은 없어요. 화면도 마찬가지
코드를 치기 전에 <strong>네모와 선만으로 구조를 그리는 기술</strong> 와이어프레임입니다.
코스가 끝나면 종이 장으로 화면을 설계하고, 피그마로 나만의 부품 상자까지 만들 있어요.
</p>
<div className="chip-row">
<span className="chip">예상 소요 50</span>
<span className="chip">실습 2 (종이 + 피그마)</span>
<span className="chip">준비물: 종이··피그마 무료 계정</span>
</div>
</div>
{/* 섹션 바로가기 */}
<nav className="pill-nav">
{SECTIONS.map((s) => (
<a key={s.n} href={`#sec-${s.n}`}>
<b>{s.n}</b>
{s.label}
</a>
))}
</nav>
<Section n={1} title="와이어프레임 = 건축 도면" sub="색도 꾸밈도 없이, 구조만 그린다">
<p>
<strong>와이어프레임(Wireframe)</strong> 화면의 <strong>뼈대 그림</strong>이에요.
어디에 메뉴가 있고, 어디에 목록이 오고, 버튼은 개인지 <strong>위치와 크기</strong>
네모와 선으로 그립니다. , 예쁜 폰트, 사진은 일부러 뺍니다.
</p>
<p>
건축 도면과 완전히 같은 역할이에요. 도면에는 벽지 색이 없죠. 대신 벽이 어디 서고
문이 어느 쪽으로 열리는지가 있어요. 단계에서 "화장실이 현관에서 너무 머네"
발견하면 지우개로 고치면 끝이지만, 집을 짓고 발견하면 벽을 부숴야 합니다.
</p>
<Code>{CODE_BLUEPRINT}</Code>
<div className="tip">
<b> 정의</b> 와이어프레임은 <strong>"무엇이 어디에 있는가"</strong>에만 답하는
그림이에요. "얼마나 예쁜가" 다음 단계(디자인 시안) 질문입니다.
</div>
</Section>
<Section n={2} title="왜 일부러 대충 그릴까" sub="빨리 버리기 위해서">
<p>
와이어프레임의 목적은 그리는 아니라 <strong>빨리 버리는 </strong>이에요.
아이디어가 최고인 경우는 거의 없거든요. 좋은 배치는 보통 여러 개를 그려 놓고
비교하다가 나옵니다. 그러려면 버리는 <strong>하나도 아깝지 않을 만큼 싸야</strong> 해요.
</p>
<Code>{CODE_LOFI}</Code>
<p>
비유하면 <strong>연필 스케치와 유화</strong> 차이예요. 화가도 유화 물감을 짜기 전에
연필로 구도를 수십 잡아 봅니다. 스케치는 찢어 버려도 아프지 않으니까요.
공들인 그림일수록 "이거 갈아엎자" 말이 목구멍에서 나옵니다
이걸 <strong>매몰 비용의 함정</strong>이라고 해요.
</p>
<div className="warn">
<b>수습 개발자가 가장 많이 하는 실수</b> 화면 배치가 확정되기 전에
React 코드부터 짜기 시작하는 . 배치가 바뀌면 코드도 갈아엎어야 해요.
<strong>그리기는 단위, 코딩은 단위</strong>라는 기억하세요. 것부터!
</div>
</Section>
<Section n={3} title="손그림 → 회색 박스 → 시안" sub="3단계로 점점 또렷해진다">
<p>
와이어프레임은 번에 완성하는 아니라 <strong>3단계로 해상도를 올려</strong> 가요.
흐릿한 사진의 초점이 점점 맞아 가는 것처럼, 단계마다 확정하는 것이 다릅니다.
</p>
<Code>{CODE_3STEPS}</Code>
<p>
단계마다 <strong>결정하는 사람의 질문</strong> 달라진다는 포인트예요.
손그림 앞에서는 "이 기능이 필요한가?", 회색 박스 앞에서는 "이 배치가 편한가?",
시안 앞에서는 "이 색과 느낌이 맞나?" 물어요. 순서를 지키면 단계 결정을
되돌리는 일이 줄어듭니다.
</p>
<div className="tip">
<b>실무 감각</b> 어썸데브에서 기능 회의를 하면 화이트보드에 손그림부터 그려요.
회의 중에 그리고, 회의 중에 지우죠. 피그마를 여는 방향이 정해진 <strong>다음</strong>입니다.
</div>
</Section>
<Section n={4} title="화면 부품 어휘 익히기" sub="헤더·카드·모달·탭 — 이름을 알면 대화가 빨라진다">
<p>
화면은 가지 <strong>정해진 부품의 조합</strong>이에요. 레고에 기본 블록이 있듯이
화면에도 세계 공통의 블록 이름이 있습니다. 이름을 알면 팀과의 대화도,
와이어프레임에 이름표를 다는 것도 빨라져요.
</p>
<Code>{CODE_VOCAB}</Code>
<p>
어휘는 나중에 React 컴포넌트 이름과 그대로 이어져요.
<span className="icode">&lt;Header /&gt;</span>, <span className="icode">&lt;CourseCard /&gt;</span>,
<span className="icode">&lt;LoginModal /&gt;</span> 와이어프레임에서 부품을 쪼갠 사람이
컴포넌트도 쪼갭니다. 도면의 구분이 그대로 벽이 되는 것과 같아요.
</p>
<div className="tip">
<b> 훈련</b> 오늘부터 아무 사이트나 때마다 속으로 이름을 붙여 보세요.
"저건 헤더, 저건 카드 목록, 지금 뜬 건 모달." 일주일이면 화면이 부품 단위로 보이기 시작해요.
</div>
</Section>
<Section n={5} title="플로우 그리기" sub="화면 사이의 '이동'을 화살표로">
<p>
화면 장을 그렸어도 반쪽짜리예요. 사용자는 화면 <strong>사이를 이동</strong>하니까요.
화면들을 화살표로 잇고, 화살표 위에 "무엇을 하면 넘어가는지" 적은 그림을
<strong> 플로우(Flow)</strong>라고 합니다. 지하철 노선도에서 (화면)
노선(이동) 함께 그리는 것과 같아요.
</p>
<Code>{CODE_FLOW}</Code>
<p>
플로우의 진짜 가치는 <strong>구멍 찾기</strong>예요. "실패하면?", "결과가 없으면?",
"뒤로 가면?" 화살표를 그리다 보면 미처 생각 화면이 드러나요.
구멍을 코딩 <strong>전에</strong> 찾으면 10분짜리 수정이고,
코딩 <strong>후에</strong> 찾으면 야근입니다.
</p>
<div className="warn">
<b>해피 패스의 함정</b> 모든 풀리는 경로(해피 패스) 그리고 끝내지 마세요.
실제 사용자는 오타를 내고, 뒤로 가기를 누르고, 검색 결과 0개를 만납니다.
에러· 화면·로딩까지 그려야 진짜 설계예요.
</div>
</Section>
<Section n={6} title="직접 확인해 보기 ① — 우리 플랫폼 역설계" sub="완성된 화면을 도면으로 되돌리는 연습">
<p>
설계 연습의 가장 좋은 교재는 <strong>이미 만들어진 화면</strong>이에요.
마침 여러분 눈앞에 하나 있죠 지금 보고 있는 학습 플랫폼입니다.
완성된 화면에서 색과 글자를 머릿속으로 지우고 <strong>뼈대만 종이에 옮기는</strong>
역설계를 봅시다.
</p>
<Code>{CODE_REVERSE}</Code>
<p>
역설계를 하면 가지가 보여요. 첫째, 만든 화면일수록 부품이
<strong> 규칙적으로 반복</strong>된다는 (카드가 같은 크기죠?).
둘째, 여백도 <strong>설계의 일부</strong>라는 아무것도 없는 자리가
우연이 아니라 의도라는 그려 보면 알게 됩니다.
</p>
<div className="tip">
<b> 단계 </b> 그렸다면 플랫폼의 <strong>플로우</strong> 그려 보세요.
로그인 학습 센터 코스 페이지(지금 여기!)까지, 화살표 위에 조건을 적으면서요.
섹션 5에서 배운 실전 화면에 바로 적용하는 겁니다.
</div>
</Section>
<Section n={7} title="직접 확인해 보기 ② — 피그마로 와이어프레임 키트 만들기" sub="회색 부품 5개짜리 나만의 레고 상자">
<p>
손그림 다음 단계인 <strong>회색 박스</strong> 도구가 필요해요. 실무 표준은
<strong> 피그마(Figma)</strong> 브라우저에서 바로 돌아가고 무료 계정으로 충분합니다.
오늘은 화면을 그리기 전에, 매번 다시 있는 <strong>부품 키트</strong>부터 만들어요.
레고 놀이 전에 블록 상자부터 채우는 셈이죠.
</p>
<Code>{CODE_FIGMA_KIT}</Code>
<p>
키트가 완성됐다면 조립 과제: 섹션 6에서 종이에 그린 <strong>우리 플랫폼 역설계</strong>
피그마 키트로 다시 조립해 보세요. 손그림(1단계) 회색 박스(2단계) 올리는,
실무에서 매일 일어나는 바로 흐름입니다.
</p>
<Code>{CODE_CHECK}</Code>
<div className="tip">
<b>공유하는 </b> 완성한 피그마 파일은 오른쪽 <span className="kbd">Share</span> 버튼으로
링크를 만들어 멘토에게 보여 주세요. 피드백은 파일 위에 바로 댓글로 달려요
이것도 실무에서 디자이너·개발자가 협업하는 방식 그대로입니다.
</div>
</Section>
<Section n={8} title="7주차 종합 프로젝트와의 연결" sub="이 코스는 그날을 위한 준비 운동">
<p>
지금 와이어프레임을 배울까요? <strong>7주차 종합 프로젝트</strong>에서 여러분은
팀으로 서비스를 하나 기획하고 만들게 돼요. 그때 가장 먼저 하는 일이 바로
오늘 배운 것들입니다.
</p>
<ol className="olist">
<li>아이디어가 모이면 <strong>손그림</strong>으로 화면 후보를 쏟아낸다 (섹션 2·3)</li>
<li>팀이 방향을 고르면 <strong>플로우</strong> 화면 사이 이동과 구멍을 찾는다 (섹션 5)</li>
<li>구조가 정해지면 <strong>피그마 회색 박스</strong> 배치를 확정한다 (섹션 7)</li>
<li>그다음에야 React 컴포넌트로 옮긴다 부품 이름 그대로! (섹션 4)</li>
</ol>
<p>
순서를 지키는 팀과 지키는 팀은 프로젝트 후반에 차이가 벌어져요.
도면 없이 시작한 팀은 마지막 주에 벽을 부수고 있고, 도면부터 그린 팀은
꾸미기와 다듬기에 시간을 씁니다.
</p>
<div className="tip">
<b>지금 </b> 섹션 6·7 실습 결과물(종이 사진 + 피그마 링크) 버리지 말고
모아 두세요. 7주차 기획 회의 "나 이거 할 줄 알아" 증거이자,
팀에서 와이어프레임 담당을 맡을 있는 포트폴리오가 됩니다.
</div>
</Section>
<div className="step-card">
<div className="step-body">
<h3 style={{ marginBottom: 6 }}>📐 여기까지 왔다면</h3>
<p className="muted">
이제 화면을 만나면 색보다 <strong>구조</strong> 먼저 보일 거예요. 네모와 화살표만으로
서비스를 설계하는 언어를 익힌 거죠. 다음은 도면을 진짜 화면으로 세우는 차례 {' '}
<Link to="/learn/coding"><strong>코딩 기초</strong></Link> 코스에서 와이어프레임의 부품이
어떻게 React 컴포넌트가 되는지 이어서 배워 보세요. 그리고 7주차 종합 프로젝트에서
오늘 만든 피그마 키트를 꺼내 쓰는 , 잊지 말기!
</p>
</div>
</div>
</div>
);
}