바이브코딩을 위한 최소한의 웹개발지식
1. 서론
1.1 왜 이 강의를 만들게 되었는가?
- 바이브코딩을 처음 접한 비개발자는 쉽습니다
- “엑셀파일 하나 클로드한테 주고, 이 판매율 대시보드 시각화해줘~”
- 처음에는 결과가 너무 잘 나옴, UI도 나름 깔끔하고 기능도 잘 동작하는 듯함. 배포도 어렵지 않음. publish 버튼 누르면 바로 배포되거나 vercel 연동하면 배포까지 끝
- 그런데 대부분 3일 안에 벽을 마주하게 됩니다.
- 내가 만든 서비스를 사용하다 보니 이런저런 오류들이 나기 시작
- 복잡한 기능들을 연동하다 보면 뭐가 뭔지 모르겠음
- 클로드가 “백엔드는 Express.js, 프론트엔드는 React.js, DB는 연동할까요? 아니면 excel과 같은 파일 형식을 사용하겠습니까?” 와 같은 어려운 용어들을 말하기 시작하는데 어떻게 답해야할지 모르겠음
- “AI 딸깍? 금방 한계가 옵니다”
- 바이브코딩으로 복잡한 서비스를 만들고자 시도해보신 분들은 아시겠지만, 99%의 바이브코더들은 위와 같은 문제를 겪게 됩니다.
1.2 왜 바이브코딩은 여전히 어려운가?
- 현재 시장에 나와있는 AI 코딩 툴들은 타겟을 기준으로 크게 2종류로 나눌 수 있습니다.
- 개발자를 위한 툴 : Claude code, Codex, Gemini cli, Cursor 등
- 비개발자를 위한 툴 : Lovable, Base44, Replit, Google AI Studio 등
- 비개발자분들은 대부분 진입장벽이 낮고 접근하기 쉬운 Lovable, Replit 같은 툴들을 사용하게 됩니다.
- 그런데 이런 툴들은 코드가 복잡해지면 관리가 어렵고, 크레딧에 지나치게 의존하는 등 한계가 명확합니다.
- 결국 개발자를 위한 툴을 사용해야 하고, 그 툴들은 배경지식의 유무에 따라 결과물 수준 차이가 극명합니다.
1.3 그럼 뭘 알아야 하는가?
- 기초 컴퓨터과학 이론
- 기초 웹구조 이론
컴퓨터과학(CS)는 소프트웨어가 어떻게 동작하는지를 이해하게 도와주고, 웹구조 이론은 웹상에서 어떻게 이 소프트웨어들을 활용하는지 알 수 있게 합니다.
이 2가지만 알아도 AI에게 ‘질문’하고 ‘지시’하는 관리자의 위치에 설 수 있게 됩니다.
* 이번 강의에서는 시간 관계상, 기초 웹구조 이론만 다루도록 하겠습니다.
2. 본론
2.1 웹구조 이론
2.1.1 태초에 '문서'만 주고받던 시절 (HTML과 정적 웹)
- 스토리: 1980년대 후반, 스위스 제네바의 CERN 입자물리학 연구소에서 과학자들이 컴퓨터를 사용해 연구자료와 논문을 작성하고 기록하고 있었어요. 문제는 연구 자료들을 서로 공유하기가 너무 어려웠다는 건데요, 파일을 옮기기가 매우 불편했어요.
팀 버너스 리는 이 문제를 해결하기 위해 ‘하이퍼텍스트(HyperText)라는 개념을 만들었습니다. 문서 안의 특정 단어(링크)를 누르면 그 단어와 관련된 다른 문서로 즉시 이동하게 하는 방식이었죠.
그가 만든 HTML(HyperText Markup Language)는 단순 텍스트에 ‘마크 업’, 즉 빨간펜으로 표시하듯 태그를 넣어서 컴퓨터가 읽을 수 있도록 표시한 언어이고, 태그마다 링크, 제목, 리스트 등의 역할이 부여되어 있었어요.
- 배우는 개념: HTML의 본질, 초기 웹의 한계, 정적(Static) 웹사이트
- 시각 자료
실습 코드 1-1
<!DOCTYPE html> <html> <head> <title>모바코 - 모여서 각자 바이브코딩</title> </head> <body bgcolor="#ffffff" text="#000000" link="#0000ee" vlink="#551a8b" alink="#ff0000"> <center> <font face="Times New Roman" size="6" color="#000080"> <b>모바코</b> </font> <br> <font size="4"><b>모여서 각자 바이브코딩</b></font> <br> <font size="2">이 페이지는 넷스케이프 내비게이터 3.0 이상에서 제일 잘 보입니다</font> </center> <hr size="3" width="80%"> <center> <marquee width="75%">이번 주도 모여서 각자 만들고, 막히면 같이 봅니다!!! 노트북과 충전기를 꼭 챙겨오세요!!!</marquee> </center> <p> <font size="4"><b>모임 소개</b></font> <br> 모바코는 같은 공간에 모여 각자 만들고 싶은 것을 바이브코딩하는 작은 모임입니다. 정해진 강의나 발표보다, 손을 움직이며 만들고 서로의 작업을 구경하는 시간을 중요하게 생각합니다. </p> <hr> <center> <font size="5"><b>다음 모임 안내</b></font> </center> <table border="1" cellpadding="6" cellspacing="0" width="100%"> <tr> <th>항목</th> <th>내용</th> </tr> <tr> <td><b>일시</b></td> <td>매주 수요일 저녁 7시 30분</td> </tr> <tr> <td><b>장소</b></td> <td>관악청년청</td> </tr> <tr> <td><b>준비물</b></td> <td>노트북, 충전기, 만들고 싶은 아이디어</td> </tr> <tr> <td><b>진행</b></td> <td>근황 공유 - 각자 코딩 - 막힌 부분 같이 보기 - 결과 구경</td> </tr> </table> <hr> <p> <font size="4"><b>최근 소식</b></font> </p> <ul> <li>1998-11-03: 모바코 홈페이지를 열었습니다.</li> <li>1998-11-02: 다음 모임에서는 개인 프로젝트를 1시간 이상 진행합니다.</li> <li>1998-10-31: 새 회원을 환영합니다. 처음 오셔도 괜찮습니다.</li> </ul> <hr> <center> <font size="5"><b>회원 명단</b></font> </center> <table border="1" cellpadding="6" cellspacing="0" width="100%"> <tr> <th>이름</th> <th>관심 분야</th> <th>이번에 만드는 것</th> </tr> <tr> <td>준호</td> <td>웹 서비스</td> <td>초창기 웹 느낌의 모임 홈페이지</td> </tr> <tr> <td>익명의 개발자</td> <td>AI 도구</td> <td>작업 자동화 스크립트</td> </tr> <tr> <td>새 회원</td> <td>아직 정하는 중</td> <td>작은 토이 프로젝트</td> </tr> </table> <hr> <p> <font size="4"><b>모바코 규칙</b></font> </p> <ol> <li>각자 만들고 싶은 것을 가져옵니다.</li> <li>막히면 조용히 오래 끙끙대기보다 옆 사람에게 물어봅니다.</li> <li>남의 작업을 평가하기보다, 더 잘 움직이게 만드는 데 집중합니다.</li> <li>끝나기 전에 오늘 만든 것을 짧게 공유합니다.</li> </ol> <hr size="3" width="80%"> <center> <font size="2"> 당신은 방문자 번호 <b>00000404</b> <br> 마지막 고침: 1998년 11월 3일 <br> <a href="mailto:mobaco@example.com">모바코 운영자에게 전자우편 보내기</a> | <a href="#">링크 모음</a> | <a href="#">처음으로</a> </font> </center> </body> </html>
2.1.2 문서를 다른 사람들과 공유하기 (HTTP, 클라이언트-서버 구조)
- 스토리: 이제 HTML로 문서 간 이동이 가능해진 후, 팀 버너스 리는 멀리 떨어져 있는 컴퓨터에 저장된 HTML 문서를 내 컴퓨터로 물리적 이동 없이 편하게 읽어오는 것이 필요했습니다. 인터넷을 통해 문서 파일을 전송하고 싶었는데, 이 전송을 위한 ‘통신 규약’을 만들어야 했죠. 그래서 만들게 된 통신 규약(프로토콜)이 HTTP입니다.
HTTP는 누군가(컴퓨터)가 요청을 보내면 그에 맞춰 반드시 응답을 해주는 '요청-응답' 구조를 기반으로 하는데요, 이때 요청을 보내는 주체를 ‘클라이언트(고객)’, 응답을 하는 주체를 ‘서버(종업원)’이라고 부르죠.
지금 우리가 사용하는 대부분의 웹은 HTTP를 사용하는 클라이언트-서버 구조로 이루어져 있습니다.* 초기에 팀 버너스 리는 자신의 컴퓨터를 서버로 사용했고, 24시간 꺼지지 않게 유지했어요.
- 배우는 개념: 클라이언트와 서버의 탄생, HTTP 요청(Request)과 응답(Response)
- 시각자료
URL이란?
팀 버너스 리는 HTML, HTTP와 더불어 URL이라는 개념도 만들었습니다. URL은 Uniform Resource Locator의 약자로, 아주 쉽게 직역하면 "인터넷에 있는 자원(Resource)의 일관된 위치 고유 식별자"라는 뜻입니다.
우리가 주소창에 치는 URL은 사실 여러 가지 의미를 가진 조각들이 정교하게 결합한 형태입니다.
예시 주소를 통해 구조를 낱낱이 파헤쳐 볼게요.
https://www.example.com:443/blog/posts?id=5&page=1
https://— 프로토콜 (통신 규약)- 의미: "이 주소로 갈 때 어떤 통신 규칙을 쓸 것인가?"를 정합니다.
- 스토리 연결: 2단계에서 배운 그 HTTP 맞습니다!
https는 HTTP에 보안(Secure)을 강화해 데이터를 암호화해서 주고받겠다는 뜻입니다.
-
www.example.com— 호스트 이름 / 도메인 (컴퓨터 이름)- 의미: "전 세계 수많은 인터넷 컴퓨터 중 정확히 어느 컴퓨터(서버)로 가야 하는가?"를 나타냅니다.
- 스토리 연결: 6단계에서 다룰 '인터넷 건물에 붙인 가독성 좋은 간판'입니다.
-
:443— 포트 번호 (Port)- 의미: "그 컴퓨터(건물)에 도착해서 몇 번 문(창구)으로 들어가야 하는가?"를 뜻합니다.
- 특징: 보통 웹 서비스는 80번(HTTP)이나 443번(HTTPS) 문을 기본으로 사용하기 때문에, 우리가 주소창에 칠 때는 브라우저가 자동으로 생략해 줍니다. (예: 은행 건물에 갔을 때 1층 대출 창구로 바로 가도록 안내되는 것과 같습니다.)
-
/blog/posts— 경로 (Path)- 의미: "컴퓨터 서버 안에 있는 수많은 폴더 중 어느 폴더의 파일인가?"를 나타냅니다.
- 내 컴퓨터에서
C드라이브 > 문서 > 사진 > 고양이.jpg순으로 찾아 들어가는 폴더 경로와 완전히 같은 개념입니다.
-
?id=5&page=1— 쿼리 스트링 (Query String / 파라미터)- 의미: "그 페이지를 보여줄 때, 이런 조건(옵션)을 추가해서 보여줘!"라고 서버에 요청하는 보충 설명입니다.
- 구조:
?기호로 시작하며,Key=Value형태로 데이터가 엮입니다.- 위의 예시는 "블로그 포스트 중에서 id가 5번인 글을 보여주고, 댓글은 1페이지를 보여줘"라는 뜻이 됩니다. 쇼핑몰에서 상품을 정렬하거나 검색할 때 주소창 뒤에 복잡하게 붙는 글자들이 바로 이것입니다.
2.1.3 웹사이트의 시각적/동적 진화 (CSS/JavaScript)
- 스토리 1: 팀 버너스 리가 만든 초기 웹은 흰 종이에 검은 글씨, 파란 밑줄이 그어진 링크가 전부인 딱딱한 '전자 문서'였습니다. 하지만 웹이 대중화되면서 기업과 유저들이 폭발적으로 늘어나자 새로운 불만이 터져 나왔습니다. "디자인을 예쁘게 꾸미고 싶어!", "화면이 인쇄된 종이처럼 멈춰있지 않고 살아 움직이게 해줘!" 같은 요구였죠.
원래 논문 구조만 짜라고 만든 HTML은 이 요구를 감당하지 못했습니다. 태그 안에 style을 만들어 그 안에 디자인 정보를 넣었지만, 유지보수가 너무나 불편했거든요. 그래서 개발자들은 문서의 뼈대(HTML)와 시각적인 디자인을 완전히 분리한 CSS를 발명해 웹을 화려하게 물들이기 시작했습니다.
- 스토리 2: 1995년, 가장 잘나가던 웹 브라우저 회사였던 Netscape(현 FireFox)에 근무하던 브렌던 아이크는 상사로부터 황당한 미션을 받습니다.
"웹페이지 안에서 글자가 움직이고 팝업창도 뜨게 만드는 '가벼운 프로그래밍 언어'를 마이크로소프트가 따라 하기 전에 얼른 만들어와!”
시간이 너무 촉박했던 그는 단 10일 만에 새로운 언어의 설계부터 프로토타입 개발까지 전부 끝내버립니다. 그리고 이 언어의 이름을 당시 인기있던 언어인 Java를 따라해 Javascript라고 명명하게 됩니다.
이후 브라우저들은 Javascript를 해석해서, HTML 코드들을 조작할 수 있는 기능들을 탑재하게 됩니다.* 이때 브라우저는 HTML을 DOM이라는 트리 형태의 데이터 구조로 만들고, 이를 조작합니다.
- 배우는 개념: HTML과 CSS 분리, JavaScript의 역할, 정적 웹에서 동적 웹으로의 패러다임 전환
실습 코드 3-1
<!DOCTYPE html> <html> <head> <title>모바코 - 모여서 각자 바이브코딩</title> </head> <body style="margin:0; padding:24px; background:#e7edf8; color:#222222; font-family:Arial, Helvetica, sans-serif;"> <div style="width:760px; margin:0 auto; background:#ffffff; border:3px double #335599; padding:18px;"> <div style="text-align:center; background:#003366; color:#ffffff; padding:18px; border:2px solid #001f3f;"> <div style="font-size:42px; font-weight:bold; letter-spacing:0;">모바코</div> <div style="font-size:18px; font-weight:bold; margin-top:6px;">모여서 각자 바이브코딩</div> <div style="font-size:12px; margin-top:10px; color:#dbe8ff;">인라인 스타일 시대 특별판</div> </div> <div style="margin-top:12px; padding:8px; background:#fff7cc; border:1px solid #d0a800; color:#663300; text-align:center; font-weight:bold;"> <marquee>이번 주도 모여서 각자 만들고, 막히면 같이 봅니다!!! 노트북과 충전기를 꼭 챙겨오세요!!!</marquee> </div> <table style="width:100%; margin-top:16px; border-collapse:collapse;"> <tr> <td style="width:180px; vertical-align:top; background:#f1f5ff; border:1px solid #99aacc; padding:12px;"> <div style="font-size:16px; font-weight:bold; color:#003366; border-bottom:1px solid #99aacc; padding-bottom:6px;">메뉴</div> <p style="margin:10px 0 0 0;"><a href="#about" style="color:#003399; font-weight:bold;">모임 소개</a></p> <p style="margin:8px 0 0 0;"><a href="#next" style="color:#003399; font-weight:bold;">다음 모임</a></p> <p style="margin:8px 0 0 0;"><a href="#news" style="color:#003399; font-weight:bold;">최근 소식</a></p> <p style="margin:8px 0 0 0;"><a href="#members" style="color:#003399; font-weight:bold;">회원 명단</a></p> <p style="margin:8px 0 0 0;"><a href="#rules" style="color:#003399; font-weight:bold;">모바코 규칙</a></p> <div style="margin-top:18px; padding:8px; background:#ffffff; border:1px dashed #7799cc; font-size:12px; line-height:1.6;"> <b>방문자 번호</b><br> <span style="font-family:'Courier New', monospace; color:#cc0000;">00000404</span> </div> </td> <td style="vertical-align:top; padding-left:16px;"> <div id="about" style="border:1px solid #99aacc; padding:14px; background:#ffffff;"> <h1 style="margin:0 0 10px 0; font-size:24px; color:#003366; border-bottom:3px solid #6699cc;">모임 소개</h1> <p style="margin:0; line-height:1.7;"> 모바코는 같은 공간에 모여 각자 만들고 싶은 것을 바이브코딩하는 작은 모임입니다. 정해진 강의나 발표보다, 손을 움직이며 만들고 서로의 작업을 구경하는 시간을 중요하게 생각합니다. </p> </div> <div id="next" style="margin-top:14px; border:1px solid #99aacc; padding:14px; background:#fbfdff;"> <h2 style="margin:0 0 10px 0; font-size:20px; color:#003366; border-bottom:2px solid #ccd8ee;">다음 모임 안내</h2> <table style="width:100%; border-collapse:collapse; background:#ffffff;"> <tr> <th style="width:120px; padding:8px; background:#335599; color:#ffffff; border:1px solid #223366;">항목</th> <th style="padding:8px; background:#335599; color:#ffffff; border:1px solid #223366;">내용</th> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc; font-weight:bold;">일시</td> <td style="padding:8px; border:1px solid #99aacc;">매주 수요일 저녁 7시 30분</td> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc; font-weight:bold;">장소</td> <td style="padding:8px; border:1px solid #99aacc;">관악청년청</td> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc; font-weight:bold;">준비물</td> <td style="padding:8px; border:1px solid #99aacc;">노트북, 충전기, 만들고 싶은 아이디어</td> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc; font-weight:bold;">진행</td> <td style="padding:8px; border:1px solid #99aacc;">근황 공유 - 각자 코딩 - 막힌 부분 같이 보기 - 결과 구경</td> </tr> </table> </div> <div id="news" style="margin-top:14px; border:1px solid #99aacc; padding:14px; background:#ffffff;"> <h2 style="margin:0 0 10px 0; font-size:20px; color:#003366; border-bottom:2px solid #ccd8ee;">최근 소식</h2> <ul style="margin:0; padding-left:22px; line-height:1.8;"> <li><b>1998-11-03</b>: 모바코 홈페이지 인라인 스타일판을 열었습니다.</li> <li><b>1998-11-02</b>: 다음 모임에서는 개인 프로젝트를 1시간 이상 진행합니다.</li> <li><b>1998-10-31</b>: 새 회원을 환영합니다. 처음 오셔도 괜찮습니다.</li> </ul> </div> <div id="members" style="margin-top:14px; border:1px solid #99aacc; padding:14px; background:#fbfdff;"> <h2 style="margin:0 0 10px 0; font-size:20px; color:#003366; border-bottom:2px solid #ccd8ee;">회원 명단</h2> <table style="width:100%; border-collapse:collapse; background:#ffffff;"> <tr> <th style="padding:8px; background:#335599; color:#ffffff; border:1px solid #223366;">이름</th> <th style="padding:8px; background:#335599; color:#ffffff; border:1px solid #223366;">관심 분야</th> <th style="padding:8px; background:#335599; color:#ffffff; border:1px solid #223366;">이번에 만드는 것</th> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc;">준호</td> <td style="padding:8px; border:1px solid #99aacc;">웹 서비스</td> <td style="padding:8px; border:1px solid #99aacc;">CSS 변천사 예제 페이지</td> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc;">익명의 개발자</td> <td style="padding:8px; border:1px solid #99aacc;">AI 도구</td> <td style="padding:8px; border:1px solid #99aacc;">작업 자동화 스크립트</td> </tr> <tr> <td style="padding:8px; border:1px solid #99aacc;">새 회원</td> <td style="padding:8px; border:1px solid #99aacc;">아직 정하는 중</td> <td style="padding:8px; border:1px solid #99aacc;">작은 토이 프로젝트</td> </tr> </table> </div> <div id="rules" style="margin-top:14px; border:1px solid #99aacc; padding:14px; background:#ffffff;"> <h2 style="margin:0 0 10px 0; font-size:20px; color:#003366; border-bottom:2px solid #ccd8ee;">모바코 규칙</h2> <ol style="margin:0; padding-left:22px; line-height:1.8;"> <li>각자 만들고 싶은 것을 가져옵니다.</li> <li>막히면 조용히 오래 끙끙대기보다 옆 사람에게 물어봅니다.</li> <li>남의 작업을 평가하기보다, 더 잘 움직이게 만드는 데 집중합니다.</li> <li>끝나기 전에 오늘 만든 것을 짧게 공유합니다.</li> </ol> </div> </td> </tr> </table> <div style="margin-top:16px; padding:10px; text-align:center; background:#003366; color:#ffffff; font-size:12px;"> 마지막 고침: 1998년 11월 3일 | <a href="mailto:mobaco@example.com" style="color:#ffff99;">모바코 운영자에게 전자우편 보내기</a> | <a href="index.html" style="color:#ffff99;">CSS 없던 시절 버전 보기</a> </div> </div> </body> </html>
실습 코드 3-2
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>모바코 - 외부 CSS 버전</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="page"> <header class="site-header"> <p class="era-label">CSS 파일 분리 시대</p> <h1>모바코</h1> <p class="tagline">모여서 각자 바이브코딩</p> </header> <nav class="site-nav" aria-label="주요 메뉴"> <a href="#about">모임 소개</a> <a href="#next">다음 모임</a> <a href="#news">최근 소식</a> <a href="#members">회원 명단</a> <a href="#rules">모바코 규칙</a> </nav> <main> <section id="about" class="panel intro"> <div> <h2>모임 소개</h2> <p> 모바코는 같은 공간에 모여 각자 만들고 싶은 것을 바이브코딩하는 작은 모임입니다. 정해진 강의나 발표보다, 손을 움직이며 만들고 서로의 작업을 구경하는 시간을 중요하게 생각합니다. </p> </div> <aside class="notice"> <strong>이번 주 공지</strong> <span>노트북과 충전기를 꼭 챙겨오세요.</span> </aside> </section> <section id="next" class="panel"> <h2>다음 모임 안내</h2> <dl class="meeting-info"> <div> <dt>일시</dt> <dd>매주 수요일 저녁 7시 30분</dd> </div> <div> <dt>장소</dt> <dd>관악청년청</dd> </div> <div> <dt>준비물</dt> <dd>노트북, 충전기, 만들고 싶은 아이디어</dd> </div> <div> <dt>진행</dt> <dd>근황 공유 - 각자 코딩 - 막힌 부분 같이 보기 - 결과 구경</dd> </div> </dl> </section> <section id="news" class="panel"> <h2>최근 소식</h2> <ul class="news-list"> <li><time datetime="1998-11-03">1998-11-03</time> 외부 CSS 버전의 모바코 홈페이지를 열었습니다.</li> <li><time datetime="1998-11-02">1998-11-02</time> 다음 모임에서는 개인 프로젝트를 1시간 이상 진행합니다.</li> <li><time datetime="1998-10-31">1998-10-31</time> 새 회원을 환영합니다. 처음 오셔도 괜찮습니다.</li> </ul> </section> <section id="members" class="panel"> <h2>회원 명단</h2> <table class="member-table"> <thead> <tr> <th>이름</th> <th>관심 분야</th> <th>이번에 만드는 것</th> </tr> </thead> <tbody> <tr> <td>준호</td> <td>웹 서비스</td> <td>CSS 변천사 예제 페이지</td> </tr> <tr> <td>익명의 개발자</td> <td>AI 도구</td> <td>작업 자동화 스크립트</td> </tr> <tr> <td>새 회원</td> <td>아직 정하는 중</td> <td>작은 토이 프로젝트</td> </tr> </tbody> </table> </section> <section id="rules" class="panel"> <h2>모바코 규칙</h2> <ol class="rule-list"> <li>각자 만들고 싶은 것을 가져옵니다.</li> <li>막히면 조용히 오래 끙끙대기보다 옆 사람에게 물어봅니다.</li> <li>남의 작업을 평가하기보다, 더 잘 움직이게 만드는 데 집중합니다.</li> <li>끝나기 전에 오늘 만든 것을 짧게 공유합니다.</li> </ol> </section> </main> <footer class="site-footer"> <span>방문자 번호 00000404</span> <a href="mailto:mobaco@example.com">모바코 운영자에게 전자우편 보내기</a> <a href="../index-inline-style.html">인라인 스타일 버전 보기</a> </footer> </div> </body> </html>* { box-sizing: border-box; } body { margin: 0; background: #eef2f7; color: #1f2933; font-family: Arial, Helvetica, sans-serif; line-height: 1.6; } a { color: #1b5bbf; font-weight: bold; text-decoration: none; } a:hover { text-decoration: underline; } .page { width: min(960px, calc(100% - 32px)); margin: 32px auto; background: #ffffff; border: 1px solid #b8c7d9; box-shadow: 0 12px 30px rgba(20, 38, 70, 0.16); } .site-header { padding: 36px 32px 32px; background: #173b6c; color: #ffffff; text-align: center; } .era-label { margin: 0 0 8px; color: #cfe0ff; font-size: 13px; font-weight: bold; } .site-header h1 { margin: 0; font-size: 48px; line-height: 1.1; } .tagline { margin: 10px 0 0; font-size: 20px; font-weight: bold; } .site-nav { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding: 12px; background: #f8d867; border-bottom: 1px solid #d4ad2f; } .site-nav a { padding: 6px 10px; color: #173b6c; border: 1px solid #c69d23; background: #fff4bd; } main { padding: 22px; } .panel { margin-top: 18px; padding: 20px; border: 1px solid #c6d3e2; background: #fbfdff; } .panel:first-child { margin-top: 0; } .panel h2 { margin: 0 0 12px; padding-bottom: 8px; color: #173b6c; border-bottom: 3px solid #8fb0d9; font-size: 24px; } .panel p { margin: 0; } .intro { display: grid; grid-template-columns: 1fr 220px; gap: 18px; align-items: start; } .notice { padding: 14px; background: #fff7d1; border: 1px solid #d4ad2f; color: #5f4500; } .notice strong, .notice span { display: block; } .notice span { margin-top: 6px; } .meeting-info { display: grid; gap: 10px; margin: 0; } .meeting-info div { display: grid; grid-template-columns: 130px 1fr; border: 1px solid #c6d3e2; background: #ffffff; } .meeting-info dt, .meeting-info dd { margin: 0; padding: 10px; } .meeting-info dt { background: #e4edf8; color: #173b6c; font-weight: bold; } .news-list, .rule-list { margin: 0; padding-left: 22px; } .news-list li, .rule-list li { margin-top: 8px; } .news-list li:first-child, .rule-list li:first-child { margin-top: 0; } .news-list time { color: #5b6b7d; font-weight: bold; } .member-table { width: 100%; border-collapse: collapse; background: #ffffff; } .member-table th, .member-table td { padding: 10px; border: 1px solid #b8c7d9; text-align: left; } .member-table th { background: #173b6c; color: #ffffff; } .member-table tr:nth-child(even) td { background: #f4f7fb; } .site-footer { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; padding: 16px; background: #173b6c; color: #ffffff; font-size: 13px; } .site-footer a { color: #fff4bd; } @media (max-width: 720px) { .page { width: 100%; margin: 0; border: 0; } .site-header { padding: 28px 18px; } .site-header h1 { font-size: 38px; } main { padding: 14px; } .intro { grid-template-columns: 1fr; } .meeting-info div { grid-template-columns: 1fr; } }
실습 코드 3-3
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>모바코 - JavaScript 버전</title> <link rel="stylesheet" href="styles.css"> <script src="script.js" defer></script> </head> <body> <div class="page"> <header class="site-header"> <p class="era-label">JavaScript 인터랙션 시대</p> <h1>모바코</h1> <p class="tagline">모여서 각자 바이브코딩</p> <button class="theme-toggle" type="button" data-theme-toggle>야간 모드</button> </header> <nav class="site-nav" aria-label="주요 메뉴"> <button type="button" class="nav-button is-active" data-tab-button="about">모임 소개</button> <button type="button" class="nav-button" data-tab-button="next">다음 모임</button> <button type="button" class="nav-button" data-tab-button="news">최근 소식</button> <button type="button" class="nav-button" data-tab-button="members">회원 명단</button> <button type="button" class="nav-button" data-tab-button="rules">모바코 규칙</button> </nav> <main> <section id="about" class="panel tab-panel is-active" data-tab-panel="about"> <div> <h2>모임 소개</h2> <p> 모바코는 같은 공간에 모여 각자 만들고 싶은 것을 바이브코딩하는 작은 모임입니다. 정해진 강의나 발표보다, 손을 움직이며 만들고 서로의 작업을 구경하는 시간을 중요하게 생각합니다. </p> </div> <aside class="status-card"> <strong>오늘의 분위기</strong> <span data-vibe-text>차분하게 각자 만들기</span> <button type="button" data-vibe-button>분위기 바꾸기</button> </aside> </section> <section id="next" class="panel tab-panel" data-tab-panel="next"> <h2>다음 모임 안내</h2> <dl class="meeting-info"> <div> <dt>일시</dt> <dd>매주 수요일 저녁 7시 30분</dd> </div> <div> <dt>장소</dt> <dd>관악청년청</dd> </div> <div> <dt>준비물</dt> <dd>노트북, 충전기, 만들고 싶은 아이디어</dd> </div> <div> <dt>진행</dt> <dd>근황 공유 - 각자 코딩 - 막힌 부분 같이 보기 - 결과 구경</dd> </div> </dl> <div class="attendance"> <p>이번 모임 참석 예정 인원</p> <strong data-attendance-count>3</strong> <button type="button" data-attendance-plus>참석할게요</button> <button type="button" data-attendance-minus>취소할게요</button> </div> </section> <section id="news" class="panel tab-panel" data-tab-panel="news"> <h2>최근 소식</h2> <ul class="news-list" data-news-list> <li><time datetime="1998-11-03">1998-11-03</time> JavaScript 버전의 모바코 홈페이지를 열었습니다.</li> <li><time datetime="1998-11-02">1998-11-02</time> 다음 모임에서는 개인 프로젝트를 1시간 이상 진행합니다.</li> <li><time datetime="1998-10-31">1998-10-31</time> 새 회원을 환영합니다. 처음 오셔도 괜찮습니다.</li> </ul> <form class="news-form" data-news-form> <label for="news-input">새 소식 미리보기</label> <input id="news-input" name="news" type="text" placeholder="예: 오늘은 AI 에디터 비교를 합니다"> <button type="submit">추가</button> </form> </section> <section id="members" class="panel tab-panel" data-tab-panel="members"> <h2>회원 명단</h2> <table class="member-table"> <thead> <tr> <th>이름</th> <th>관심 분야</th> <th>이번에 만드는 것</th> </tr> </thead> <tbody> <tr> <td>준호</td> <td>웹 서비스</td> <td>CSS 변천사 예제 페이지</td> </tr> <tr> <td>익명의 개발자</td> <td>AI 도구</td> <td>작업 자동화 스크립트</td> </tr> <tr> <td>새 회원</td> <td>아직 정하는 중</td> <td>작은 토이 프로젝트</td> </tr> </tbody> </table> </section> <section id="rules" class="panel tab-panel" data-tab-panel="rules"> <h2>모바코 규칙</h2> <ol class="rule-list"> <li>각자 만들고 싶은 것을 가져옵니다.</li> <li>막히면 조용히 오래 끙끙대기보다 옆 사람에게 물어봅니다.</li> <li>남의 작업을 평가하기보다, 더 잘 움직이게 만드는 데 집중합니다.</li> <li>끝나기 전에 오늘 만든 것을 짧게 공유합니다.</li> </ol> </section> </main> <footer class="site-footer"> <span>방문자 번호 <span data-visitor-count>00000404</span></span> <a href="mailto:mobaco@example.com">모바코 운영자에게 전자우편 보내기</a> <a href="../external-css-site/index.html">외부 CSS 버전 보기</a> </footer> </div> </body> </html>* { box-sizing: border-box; } :root { --page: #eef2f7; --surface: #ffffff; --surface-soft: #fbfdff; --text: #1f2933; --primary: #173b6c; --primary-soft: #e4edf8; --accent: #f8d867; --border: #b8c7d9; --shadow: rgba(20, 38, 70, 0.16); } body { margin: 0; background: var(--page); color: var(--text); font-family: Arial, Helvetica, sans-serif; line-height: 1.6; } body.is-dark { --page: #101827; --surface: #172033; --surface-soft: #1d2940; --text: #ecf2ff; --primary: #85a8e6; --primary-soft: #263957; --accent: #ffd86b; --border: #405472; --shadow: rgba(0, 0, 0, 0.35); } a { color: var(--primary); font-weight: bold; text-decoration: none; } a:hover { text-decoration: underline; } button { cursor: pointer; font: inherit; } .page { width: min(960px, calc(100% - 32px)); margin: 32px auto; background: var(--surface); border: 1px solid var(--border); box-shadow: 0 12px 30px var(--shadow); } .site-header { position: relative; padding: 36px 32px 32px; background: var(--primary); color: #ffffff; text-align: center; } body.is-dark .site-header { color: #101827; } .era-label { margin: 0 0 8px; font-size: 13px; font-weight: bold; } .site-header h1 { margin: 0; font-size: 48px; line-height: 1.1; } .tagline { margin: 10px 0 0; font-size: 20px; font-weight: bold; } .theme-toggle { position: absolute; top: 14px; right: 14px; padding: 7px 10px; border: 1px solid rgba(255, 255, 255, 0.65); background: rgba(255, 255, 255, 0.16); color: inherit; } .site-nav { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding: 12px; background: var(--accent); border-bottom: 1px solid var(--border); } .nav-button { padding: 7px 11px; color: #173b6c; border: 1px solid #c69d23; background: #fff4bd; font-weight: bold; } .nav-button.is-active { background: #173b6c; color: #ffffff; } main { padding: 22px; } .panel { padding: 20px; border: 1px solid var(--border); background: var(--surface-soft); } .tab-panel { display: none; } .tab-panel.is-active { display: block; } .panel h2 { margin: 0 0 12px; padding-bottom: 8px; color: var(--primary); border-bottom: 3px solid var(--border); font-size: 24px; } .panel p { margin: 0; } .intro, #about { display: grid; grid-template-columns: 1fr 220px; gap: 18px; align-items: start; } #about.tab-panel { display: none; } #about.tab-panel.is-active { display: grid; } .status-card, .attendance { padding: 14px; background: #fff7d1; border: 1px solid #d4ad2f; color: #5f4500; } .status-card strong, .status-card span { display: block; } .status-card span { margin-top: 6px; } .status-card button, .attendance button, .news-form button { margin-top: 10px; padding: 7px 10px; border: 1px solid #173b6c; background: #173b6c; color: #ffffff; font-weight: bold; } .meeting-info { display: grid; gap: 10px; margin: 0; } .meeting-info div { display: grid; grid-template-columns: 130px 1fr; border: 1px solid var(--border); background: var(--surface); } .meeting-info dt, .meeting-info dd { margin: 0; padding: 10px; } .meeting-info dt { background: var(--primary-soft); color: var(--primary); font-weight: bold; } .attendance { margin-top: 16px; } .attendance strong { display: inline-block; min-width: 44px; margin: 8px 8px 0 0; font-size: 28px; } .news-list, .rule-list { margin: 0; padding-left: 22px; } .news-list li, .rule-list li { margin-top: 8px; } .news-list li:first-child, .rule-list li:first-child { margin-top: 0; } .news-list time { color: var(--primary); font-weight: bold; } .news-form { display: grid; gap: 8px; margin-top: 18px; } .news-form input { width: 100%; padding: 9px; border: 1px solid var(--border); font: inherit; } .member-table { width: 100%; border-collapse: collapse; background: var(--surface); } .member-table th, .member-table td { padding: 10px; border: 1px solid var(--border); text-align: left; } .member-table th { background: var(--primary); color: #ffffff; } body.is-dark .member-table th { color: #101827; } .member-table tr:nth-child(even) td { background: var(--primary-soft); } .site-footer { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; padding: 16px; background: var(--primary); color: #ffffff; font-size: 13px; } body.is-dark .site-footer { color: #101827; } .site-footer a { color: inherit; } @media (max-width: 720px) { .page { width: 100%; margin: 0; border: 0; } .site-header { padding: 54px 18px 28px; } .site-header h1 { font-size: 38px; } main { padding: 14px; } #about.tab-panel.is-active { grid-template-columns: 1fr; } .meeting-info div { grid-template-columns: 1fr; } }const tabButtons = document.querySelectorAll("[data-tab-button]"); const tabPanels = document.querySelectorAll("[data-tab-panel]"); const themeToggle = document.querySelector("[data-theme-toggle]"); const attendanceCount = document.querySelector("[data-attendance-count]"); const attendancePlus = document.querySelector("[data-attendance-plus]"); const attendanceMinus = document.querySelector("[data-attendance-minus]"); const vibeText = document.querySelector("[data-vibe-text]"); const vibeButton = document.querySelector("[data-vibe-button]"); const newsForm = document.querySelector("[data-news-form]"); const newsList = document.querySelector("[data-news-list]"); const visitorCount = document.querySelector("[data-visitor-count]"); const vibes = [ "차분하게 각자 만들기", "빠르게 프로토타입 만들기", "막힌 부분 같이 디버깅하기", "서로의 결과물 구경하기" ]; let attendance = 3; let vibeIndex = 0; // 선택한 탭 이름에 맞춰 메뉴 버튼과 콘텐츠 패널의 활성 상태를 바꿉니다. function showTab(tabName) { tabButtons.forEach((button) => { button.classList.toggle("is-active", button.dataset.tabButton === tabName); }); tabPanels.forEach((panel) => { panel.classList.toggle("is-active", panel.dataset.tabPanel === tabName); }); } // 참석 예정 인원을 갱신하되, 0명 아래로 내려가지 않게 막습니다. function updateAttendance(nextCount) { attendance = Math.max(0, nextCount); attendanceCount.textContent = String(attendance); } // 입력받은 소식 문구를 최근 소식 목록의 맨 위에 새 항목으로 추가합니다. function addNewsItem(message) { const item = document.createElement("li"); const time = document.createElement("time"); time.dateTime = "1998-11-04"; time.textContent = "1998-11-04"; item.append(time, ` ${message}`); newsList.prepend(item); } tabButtons.forEach((button) => { // 메뉴 버튼을 누르면 해당 섹션만 보이도록 탭을 전환합니다. button.addEventListener("click", () => { showTab(button.dataset.tabButton); }); }); // 야간 모드 클래스를 토글하고 버튼 문구를 현재 상태에 맞게 바꿉니다. themeToggle.addEventListener("click", () => { document.body.classList.toggle("is-dark"); themeToggle.textContent = document.body.classList.contains("is-dark") ? "주간 모드" : "야간 모드"; }); // 참석 예정 인원을 1명 늘립니다. attendancePlus.addEventListener("click", () => { updateAttendance(attendance + 1); }); // 참석 예정 인원을 1명 줄입니다. attendanceMinus.addEventListener("click", () => { updateAttendance(attendance - 1); }); // 미리 준비한 분위기 문구를 순서대로 돌려 보여줍니다. vibeButton.addEventListener("click", () => { vibeIndex = (vibeIndex + 1) % vibes.length; vibeText.textContent = vibes[vibeIndex]; }); // 최근 소식 폼을 서버로 보내지 않고, 현재 페이지 목록에만 임시로 추가합니다. newsForm.addEventListener("submit", (event) => { event.preventDefault(); const input = newsForm.elements.news; const message = input.value.trim(); if (!message) { input.focus(); return; } addNewsItem(message); input.value = ""; }); const nextVisitorNumber = Number(visitorCount.textContent) + 1; visitorCount.textContent = String(nextVisitorNumber).padStart(8, "0");
지금까지의 구조 정리
- 유저가 브라우저를 통해 특정 URL에 접속한다
- 브라우저가 자동으로 HTTP 요청을 서버에 보낸다
ex) GET /index.html HTTP/1.1
- 서버는 응답으로 HTML 파일 전체를 준다
ex) HTTP/1.1 200 OK
Content-Type: text/html<!DOCTYPE html>
<html>
<head>...</head>
<body>
<header>내비게이션 바 (코드 500줄)</header>
<main>
<img src="피드이미지.jpg">
<p>피드 내용 텍스트...</p>
<button>❤️ 126개</button>
</main>
<footer>하단 정보 (코드 300줄)</footer>
</body>
</html>
- 브라우저는 받은 HTML 파일을 DOM으로 만들고, 만든 DOM을 화면에 렌더링한다
- 유저는 렌더링된 화면을 보고 상호작용한다.
2.1.4 "내가 쓴 댓글은 다 어디로 갈까?" (DB와 FE/BE 분리구조)
- 스토리 1:
Javascript 덕분에 사용자가 직접 댓글을 쓰고 '좋아요'를 누를 수 있는 살아있는 웹사이트가 되었습니다. 그런데 여기서 또다른 문제가 생깁니다. 새로고침했더니 쓴 글들이 전부 사라져버려요. 브라우저가 Javascript를 사용해서 조작한 것은 브라우저 메모리 상의 DOM일 뿐, 실제 HTML 파일이 아니기 때문이죠. 그렇다면 우리는 유저들이 회원가입을 하고, 댓글을 쓰고, 주문한 그 수많은 정보들은 도대체 어디에 저장해야 할까요?그냥 컴퓨터에 일반 텍스트 파일(.txt)을 하나 만들어서 거기에 무작정 글을 이어 쓰면 안 될까요? 나 혼자 쓰는 일기장이라면 상관없겠지만, 수만 명의 유저가 동시에 접속하는 웹 서비스에서는 불가능합니다. 누군가 댓글을 쓰는 동시에 다른 사람이 회원가입을 하면 텍스트 파일의 글씨가 뒤엉켜 깨져버릴 테니까요. 게다가 수백만 개의 댓글 중 '내가 쓴 댓글'만 쏙 골라내어 보여달라고 하면, 메모장 파일의 처음부터 끝까지 수백만 줄을 한 줄씩 다 읽어야 하므로 컴퓨터가 멈춰버릴 거에요.
그래서 등장한 구원투수가 바로 데이터베이스(DB)입니다. 데이터베이스는 단순히 글자를 적어두는 텍스트 파일과 달리, 데이터를 엑셀처럼 격자무늬 표(Table) 형태로 아주 체계적으로 구조화해서 저장하는 '초고속 데이터 전용 창고'입니다.
- 스토리 2:
정보가 안전하게 저장되는 창고(DB)가 생긴 것은 좋았지만, 이 창고에서 데이터를 꺼내 유저에게 보여주는 방식이 문제였습니다. 당시의 전통적인 웹 방식을 MPA(Multi Page Application)라고 부르는데요. 이 방식은 인스타그램 '좋아요' 하트를 하나 누르거나 댓글을 한 줄 쓰면, 서버가 DB 창고를 다녀온 뒤 화면 전체를 구성하는 HTML 서류를 처음부터 끝까지 새로 그려서 브라우저에 던져주었습니다.단순히 좋아요만 눌러도 화면 전체가 깜빡이는 문제가 발생하게 되었고, HTML 파일이 무거울수록 깜빡임도 심해졌죠.
이를 해결하기 위해, 자바스크립트가 유저 몰래 뒤에서 백엔드-DB를 오가며 필요한 '순수 데이터'만 쏙 주고받는 기술인 AJAX(Asynchronous JavaScript And XML)가 등장합니다.
AJAX의 등장으로 웹은 처음 접속할 때 화면 껍데기(HTML/CSS/JS)만 딱 한 번 받아두고, 이후에는 데이터만 실시간으로 부분 매끄럽게 갈아 끼우는 SPA(Single Page Application) 시대로 진화하게 됩니다. 우리가 지금 쓰는 인스타그램, 유튜브, 네이버페이 같은 부드러운 웹 서비스들이 바로 이 SPA 방식으로 만들어진 것입니다.
이렇게 MPA에서 SPA로 패러다임이 전환되면서 웹 코드 구조에도 거대한 변화가 일어납니다. 서버가 화면을 다 그려주던 시절과 달리, 이제 서버는
{ "likeCount": 126 }같은 순수한 데이터만 보내기 때문입니다.자연스럽게 브라우저가 처음에 다운로드받는 HTML 뼈대 코드는 아주 단순하게 줄어들었고, 그 대신 날아오는 데이터를 받아 화면을 실시간으로 그리고 바꾸는 Javascript의 처리 양이 폭발적으로 늘어나게 되었습니다. 하트 색을 바꾸고 화면을 조립하는 복잡한 처리를 브라우저의 Javascript가 전부 도맡게 된 것이죠.
이로 인해 Javascript 코드의 분량이 상상을 초월할 정도로 비대해졌고, 이 거대해진 코드를 전문적으로 유지보수하고 관리하는 '프론트엔드(Frontend)'라는 독자적인 영역이 생겨나게 되었습니다. 결국 웹 개발은 대분열을 맞이합니다. 서버실 안에서 안전하게 DB 창고를 관리하며 오직 데이터만을 받고 보내주는 영역은 백엔드(Backend)로, 그 데이터를 받아 유저의 컴퓨터 안에서 복잡해진 Javascript를 동적으로 처리하며 화면을 조립하는 영역은 프론트엔드(Frontend)로 독립하게 되었습니다.
- 배우는 개념: DB, MPA/SPA, FE/BE
2.1.5 "홀 직원 주방 출입 금지" (API와 JSON, 웹의 공용어)
- 스토리:
이제 화면(프론트엔드)과 데이터 처리(백엔드)가 완전히 찢어졌습니다. 여기서 2가지 중요한 규칙이 생겼는데요, 1번째, 프론트엔드는 직접 DB 데이터를 보거나 수정할 수 없고, 반드시 백엔드를 거쳐야 한다는 것이고, 2번째, 백엔드는 데이터를 직접 관리하기 때문에 외부에 노출되는 정보를 최소화하고 프론트엔드가 요구하는 정보들만 전달해야 한다는 것입니다.
이를 위해서, 특정한 HTTP를 기반으로 요청/응답 규약을 정하고 해당 규약으로만 통신하기로 했고, 그 규약을 API(Application Programming Interface)라고 부르기로 했습니다.(HTTP는 URL, 메소드 등을 조합하기에 따라 다양한 형태의 규칙을 만들 수 있기 때문에, 우리 백엔드-프론트엔드만의 규약을 만든 것이죠)또한 프론트엔드에서 ‘새 글 등록’ 요청 API로 백엔드에 데이터를 전송하거나, 백엔드에서 ‘전체 글 조회’ 응답 API로 프론트엔드로 데이터를 전송할 때, 단순한 데이터 포맷을 정해서 효율적으로 데이터를 해석할 수 있도록 만들었습니다. 이 포맷이 바로 JSON(JavaScript Object Notation)이죠.
☝왜 JSON에 Javascript가 들어갈까요?
2000년대 초반, 더글라스 크락포드(Douglas Crockford)라는 개발자가 XML이 너무 무겁고 복잡해서 짜증이 났습니다. 그래서 자바스크립트를 쓰다가 문득 깨달았습니다.
"어?Javascript에서 데이터 구조 짜는 문법(Object)이 엄청 단순하고 보기 좋은데? 이걸 그냥 전 세계 컴퓨터들이 데이터 주고받을 때 쓰는 표준 양식으로 쓰자!"
그래서 Javascript 코드 중 데이터를 표현하는 부분(노테이션, Notation)만 쏙 빼서 만든 독립적인 양식이 바로 JSON입니다.
실제로 Javascript 코드와 JSON을 비교해 보면 쌍둥이처럼 똑같이 생겼습니다.
- Javascript에서 객체(Object)를 만들 때
const user = { name: "바이브코딩", age: 25 };
- JSON으로 데이터를 보낼 때
{ "name": "바이브코딩", "age": 25 }
그렇다고 Javascript에서만 쓰는가? 아닙니다. 전 세계 모든 언어에서 다 씁니다. 자바스크립트의 문법을 '기반'으로 태어났을 뿐, 지금은 파이썬, C++, 자바, 고(Go) 등 지구상에 존재하는 모든 프로그래밍 언어가 JSON을 읽고 쓸 줄 압니다. 마치 '아라비아 숫자'가 아라비아에서 만들어졌지만 지금은 전 세계 사람이 다 쓰는 공용 수학 기호가 된 것과 같은 이치입니다.
- Javascript에서 객체(Object)를 만들 때
- 배우는 개념: API, JSON
- 시각자료
2.1.6 "내 컴퓨터 말고 전 세계 사람들에게" (도메인, DNS, 배포, 보안)
- 스토리:
내 컴퓨터(
localhost) 안에서 완벽하게 돌아가는 서비스를 만들었습니다. 이제 이 서비스를 친구들과 전 세계 사람들에게 보여줄 차례입니다.처음에는 "팀 버너스 리처럼 내 컴퓨터를 24시간 켜두고 서버로 쓰면 되지 않을까?" 생각할 수 있습니다. 하지만 이는 현실적으로 불가능합니다. 전기세나 하드웨어 수명 문제도 있지만, 무엇보다 외부 사람들에게 내 컴퓨터 접속을 허용하면서 동시에 해커들에게 털리지 않도록 완벽한 보안 환경을 구축하는 것은 전문가에게도 어려운 일이기 때문입니다. 작은 실수 하나로 내 컴퓨터의 모든 개인정보가 해킹당할 수 있죠.
그래서 등장한 것이 클라우드 배포(Deployment)입니다. Vercel이나 AWS 같은 클라우드 서비스는 24시간 절대 꺼지지 않고, 철통 보안 세팅이 이미 완벽하게 완료된 '준비된 컴퓨터'를 우리에게 빌려줍니다. 우리는 그저 클릭 몇 번으로 코드만 얹으면 안전하게 서비스를 출시할 수 있습니다.
코드를 클라우드에 올렸다면, 유저들이 쉽게 찾아올 수 있도록
vibe-coding.com같은 간판인 도메인(Domain)을 달아줍니다. 그러면 인터넷 세상의 전화번호부인 DNS(Domain Name System) 서버가 중간에서 문자 주소를 실제 클라우드 컴퓨터의 숫자 IP 주소로 연결해 주는 교통정리를 맡아줍니다.여기서 배포 시 절대로 놓쳐서는 안 되는 치명적인 보안 문제가 존재합니다. DB의 비밀번호나 외부 서비스 비밀 키(API Key) 같은 민감한 정보들을 소스코드에 그대로 적어둔 채 GitHub와 같은 공개 클라우드에 올리는 것입니다. 소스코드는 배포되는 순간 외부에 노출될 위험이 크기 때문에, 금고 비밀번호를 코드에 박아두는 것은 해커들에게 지갑을 통째로 바치는 꼴입니다.
따라서 현대식 배포에서는 실제 비밀번호나 핵심 보안 설정을 소스코드와 완전히 격리하여, 클라우드 서버의 깊숙한 안전지대(환경설정 창)에 별도로 입력해 두고 코드 안에서는 별명으로만 불러다 쓰는 보안 방식을 필수적으로 사용합니다. 이 보안 세팅까지 마쳐야 비로소 전 세계 누구나 접속할 수 있는 안전한 웹 서비스가 완성됩니다.
- 배우는 개념: 클라우드 배포, 도메인&DNS, 보안 기초
- 시각자료














