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:
parent
96f92eceba
commit
ff14183f03
@ -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 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@ -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>
|
||||
|
||||
370
frontend/src/pages/courses/AiToolsPage.jsx
Normal file
370
frontend/src/pages/courses/AiToolsPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
392
frontend/src/pages/courses/BandwidthPage.jsx
Normal file
392
frontend/src/pages/courses/BandwidthPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
404
frontend/src/pages/courses/CacheNosqlPage.jsx
Normal file
404
frontend/src/pages/courses/CacheNosqlPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
367
frontend/src/pages/courses/CicdPage.jsx
Normal file
367
frontend/src/pages/courses/CicdPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
342
frontend/src/pages/courses/DesignRefsPage.jsx
Normal file
342
frontend/src/pages/courses/DesignRefsPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
375
frontend/src/pages/courses/GpuPage.jsx
Normal file
375
frontend/src/pages/courses/GpuPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
357
frontend/src/pages/courses/MainboardPowerPage.jsx
Normal file
357
frontend/src/pages/courses/MainboardPowerPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
420
frontend/src/pages/courses/TestingPage.jsx
Normal file
420
frontend/src/pages/courses/TestingPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
330
frontend/src/pages/courses/ToolCapturePage.jsx
Normal file
330
frontend/src/pages/courses/ToolCapturePage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
397
frontend/src/pages/courses/ToolDevtoolsPage.jsx
Normal file
397
frontend/src/pages/courses/ToolDevtoolsPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
395
frontend/src/pages/courses/ToolGiteaPage.jsx
Normal file
395
frontend/src/pages/courses/ToolGiteaPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
374
frontend/src/pages/courses/ToolNotionPage.jsx
Normal file
374
frontend/src/pages/courses/ToolNotionPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
399
frontend/src/pages/courses/ToolShortcutsPage.jsx
Normal file
399
frontend/src/pages/courses/ToolShortcutsPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
382
frontend/src/pages/courses/ToolTerminalPage.jsx
Normal file
382
frontend/src/pages/courses/ToolTerminalPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
358
frontend/src/pages/courses/VpnProxyPage.jsx
Normal file
358
frontend/src/pages/courses/VpnProxyPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
360
frontend/src/pages/courses/WireframePage.jsx
Normal file
360
frontend/src/pages/courses/WireframePage.jsx
Normal 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"><Header /></span>, <span className="icode"><CourseCard /></span>,
|
||||
<span className="icode"><LoginModal /></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>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user