작은 웹게임이나 실습 페이지라면 HTML 파일 하나로 시작해도 괜찮아요. 다만 같은 스타일을 두 군데 이상 고치거나, 자바스크립트에서 기능을 찾느라 스크롤을 오래 하거나, 이미지 경로가 자주 꼬이기 시작했다면 HTML·CSS·JavaScript와 이미지 폴더를 나눌 때입니다.
이 글은 운영자의 실제 사용·운영 경험과 작성 당시 확인 가능한 자료를 바탕으로 정리했습니다. AI는 구성과 문장 편집을 보조했으며, 공개 전 사실관계·링크·적용 조건은 운영자가 최종 검토했습니다. 자세한 기준은 콘텐츠 제작·AI 활용 원칙에서 확인할 수 있습니다.
처음부터 거대한 폴더 구조를 만들 필요는 없습니다. 이 글의 기준은 프레임워크나 빌드 도구를 쓰지 않는 작은 브라우저 게임과 개인 웹 프로젝트예요. 한 파일로 완성한 뒤 어디에서 불편이 생기는지를 보고 세 파일, 기능별 폴더 순서로 자라게 하면 됩니다.
한 파일로 시작해도 되는 범위는 어디까지일까요?
HTML 안에 <style>과 <script>를 함께 넣는 방식 자체가 잘못된 것은 아닙니다. 규칙을 시험하는 짧은 예제, 버튼 한두 개가 있는 프로토타입, 인터넷 없이 전달해야 하는 단일 파일 데모라면 오히려 복사와 실행이 편해요. 파일을 더블클릭해 브라우저에서 열고 바로 결과를 확인할 수 있다는 장점도 있습니다.
중요한 기준은 줄 수가 아니라 변경의 범위입니다. 화면 하나, 스타일 한 묶음, 서로 강하게 연결된 동작 하나라면 한 파일이 내용을 따라가기 쉽습니다. 반대로 같은 색상이나 버튼 동작을 여러 화면에서 재사용한다면 한 파일을 유지하는 이점이 빠르게 줄어들어요.
- 학습용 예제라서 HTML·CSS·JavaScript의 관계를 한눈에 보고 싶을 때
- 게임 규칙과 화면이 단순하고 다른 페이지에서 코드를 재사용하지 않을 때
- 파일 하나를 메신저나 USB로 전달하는 것이 중요한 결과물일 때
- 아직 무엇을 나눠야 할지 알 수 없는 첫 번째 작동 버전을 만들 때
이 단계에서는 폴더 설계보다 작동하는 결과를 먼저 만드는 편이 낫습니다. 분리 자체가 목표가 되면 초보자는 파일 사이를 오가느라 게임 규칙을 놓치기 쉬워요. 대신 HTML의 구조, CSS의 모양, JavaScript의 동작이 각각 어디에 있는지만 주석과 일정한 배치로 구분해 두세요.
분리할 시점을 알려 주는 네 가지 신호
첫째, 수정할 위치를 찾기 어려워졌다면 분리가 필요합니다. 점수판 색을 바꾸려고 긴 HTML에서 <style>을 찾고, 다시 버튼 동작을 고치려고 맨 아래 <script>로 이동하는 일이 반복된다면 파일 하나의 편리함이 사라진 상태예요.
둘째, 같은 코드를 복사하기 시작했다면 재사용 단위를 나눠야 합니다. 시작 화면과 게임 화면에 같은 버튼 스타일을 각각 붙여 넣었다면 styles.css 하나로 옮기는 편이 안전해요. MDN도 외부 스타일시트는 하나의 CSS 파일을 여러 페이지에 연결할 수 있고, 내부 스타일을 페이지마다 반복할 때 생기는 유지보수 부담을 줄인다고 설명합니다.
셋째, 오류의 원인을 구분하기 어려우면 역할별 파일이 도움이 됩니다. 화면 요소가 없어서 자바스크립트가 실패한 것인지, CSS 선택자가 틀린 것인지, 이미지 경로가 틀린 것인지 한 파일에서는 서로 뒤섞여 보일 수 있어요. 파일을 나누면 브라우저 개발자 도구의 Console과 Network에서 실패한 자원을 더 빨리 찾을 수 있습니다.
넷째, 기능이 서로 독립적으로 변하기 시작하면 JavaScript도 나눌 수 있습니다. 입력 처리, 점수 계산, 저장 기능을 하나의 긴 game.js에서 계속 키우기보다 각 기능의 경계가 분명해졌을 때 모듈로 옮기는 방식이에요. 다만 함수 두 개마다 파일을 만드는 식의 과도한 분리는 오히려 흐름을 찾기 어렵게 만듭니다.
처음 나눌 때는 세 파일이면 충분합니다
첫 분리에서 필요한 것은 복잡한 도구가 아니라 index.html, styles.css, game.js 세 파일입니다. 이미지가 있다면 images 폴더만 추가하세요. MDN의 초보자용 파일 안내도 웹 프로젝트에 이미지, 스타일, 스크립트 폴더를 두는 기본 구조를 제시합니다.
my-game/
├── index.html
├── styles.css
├── game.js
└── images/
├── player.png
└── background.png
HTML에는 문서 구조와 연결 정보만 남깁니다. CSS는 <head> 안에서 외부 스타일시트로 연결하고, JavaScript는 defer를 붙여 연결하면 HTML 해석이 끝난 뒤 실행되므로 화면 요소를 찾지 못하는 초보적인 오류를 줄일 수 있어요.
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<script defer src="game.js"></script>
</head>
MDN의 <script> 문서에 따르면 defer를 사용한 일반 스크립트는 문서가 해석된 뒤 실행되고, 여러 개라면 문서에 적힌 순서를 유지합니다. 반면 async는 다운로드가 끝나는 즉시 실행되며 실행 순서를 보장하지 않아요. 게임 코드가 화면의 버튼과 캔버스를 찾아야 한다면 처음에는 defer가 이해하기 쉽습니다.
분리한 뒤 화면이 깨질 때는 경로부터 보세요
파일 분리 후 가장 흔한 실패는 코드 내용보다 경로에서 생깁니다. href="styles.css"는 HTML과 CSS가 같은 폴더에 있다는 뜻이고, src="images/player.png"는 HTML이 있는 위치에서 images 폴더로 들어가라는 뜻이에요. 파일을 옮겼다면 연결 경로도 함께 바뀌어야 합니다.
운영체제에 따라 대소문자를 느슨하게 처리하는 경우가 있지만 웹 서버는 대소문자를 구분할 수 있습니다. 로컬에서 Player.png가 열렸더라도 서버에서 player.png로 요청하면 404가 날 수 있어요. MDN은 파일과 폴더 이름을 소문자로 쓰고, 공백 대신 하이픈을 사용하는 습관을 권합니다.
- 개발자 도구를 열고 Network에서 빨간색 또는 404 요청을 찾습니다.
- 실제 파일 이름과 요청 주소의 대소문자를 한 글자씩 비교합니다.
- HTML을 기준으로 같은 폴더인지, 하위 폴더인지 확인합니다.
- CSS 안의 이미지 경로는 HTML이 아니라 CSS 파일 위치를 기준으로 계산한다는 점을 확인합니다.
- 수정 후 강력 새로고침으로 이전 CSS 캐시가 남아 있는지 구분합니다.
특히 네 번째 항목이 자주 놓치는 부분입니다. styles/main.css 안에서 ../images/background.png를 썼다면 기준점은 styles 폴더예요. HTML에서 보이는 폴더 구조만 보고 경로를 작성하면 배경 이미지만 사라지는 일이 생깁니다.
JavaScript 모듈은 기능 경계가 보일 때 도입하세요
game.js가 길어졌다는 이유만으로 바로 여러 파일로 쪼갤 필요는 없습니다. 점수 계산이 화면 표시와 독립적으로 시험 가능하거나, 키보드·터치 입력을 다른 게임에서도 쓸 수 있거나, 저장 기능이 별도의 책임을 갖게 될 때가 좋은 시점이에요.
my-game/
├── index.html
├── styles/
│ └── main.css
├── scripts/
│ ├── main.js
│ ├── input.js
│ ├── score.js
│ └── storage.js
└── images/
이때 HTML에서는 <script type="module" src="scripts/main.js"></script>처럼 시작 파일 하나만 연결하고, main.js가 필요한 기능을 상대 경로로 불러오게 할 수 있습니다. MDN은 모듈 스크립트가 기본적으로 지연 실행되며, 가져올 때 상대 또는 절대 URL을 정확히 써야 한다고 안내합니다.
다만 모듈을 로컬 파일로 직접 열면 브라우저의 보안 정책 때문에 CORS 오류가 날 수 있습니다. 한 파일이나 일반 defer 스크립트에서는 잘 되던 코드가 type="module"로 바꾼 뒤 멈췄다면 코드부터 고치지 말고 로컬 웹 서버로 실행했는지 확인하세요. VS Code의 간단한 서버 확장이나 개발 환경이 제공하는 미리보기 서버를 이용하면 됩니다.
한 파일과 분리 구조 중 무엇이 더 빠를까요?
초보 프로젝트에서는 파일 개수만으로 속도를 단정하기 어렵습니다. 외부 파일은 추가 요청이 필요하지만 캐시와 재사용의 이점이 있고, 실제 배포 환경에서는 서버와 빌드 도구가 파일을 압축하거나 묶을 수도 있어요. 따라서 “요청 수가 적으니 무조건 한 파일이 빠르다”거나 “분리했으니 자동으로 성능이 좋아진다”는 결론은 피해야 합니다.
개발할 때의 구조와 배포할 때의 전달 형태도 구분할 수 있습니다. 사람은 역할별 파일에서 코드를 읽고 고치고, 배포 도구는 필요에 따라 압축된 결과물을 만들 수 있어요. 아직 빌드 도구가 필요하지 않은 작은 게임이라면 성능 추측보다 개발자 도구에서 실제 로딩 시간과 오류를 확인하는 편이 정확합니다.
처음부터 만들지 않아도 되는 폴더도 있습니다
components, utils, services, assets 같은 이름은 프로젝트가 커지면 유용하지만 빈 폴더부터 만들어 둘 필요는 없어요. 무엇을 넣을지 설명할 수 없는 폴더는 아직 필요하지 않은 경우가 많습니다. 파일 한 개뿐인 기능을 여러 단계의 폴더에 숨기면 초보자에게는 탐색 비용만 늘어납니다.
또한 final, final2, real-final처럼 파일을 복제해 버전을 관리하지 마세요. 실험 전 복사본이 필요하다면 날짜나 목적을 명확히 적고, 프로젝트가 계속 자란다면 Git 같은 버전 관리 도구를 배우는 편이 안전합니다. 버전 관리는 폴더 구조와 별개의 문제이므로 이번 단계에서 억지로 도입할 필요는 없습니다.
분리 작업은 한 번에 옮기고 바로 검증하세요
가장 안전한 순서는 먼저 원본 한 파일을 복사해 보관하고, CSS만 옮겨 화면을 확인한 다음, JavaScript를 옮겨 동작을 확인하는 것입니다. 두 종류를 동시에 옮긴 뒤 화면과 기능이 모두 깨지면 원인을 나누기 어려워져요.
- 현재 한 파일 버전이 정상 작동하는지 확인하고 복사본을 보관합니다.
<style>안의 내용만styles.css로 옮기고<link>로 연결합니다.- 새로고침해 글꼴, 배치, 배경 이미지가 이전과 같은지 확인합니다.
<script>안의 내용만game.js로 옮기고defer로 연결합니다.- 키보드·터치·점수·재시작처럼 사용자가 누르는 모든 동작을 다시 시험합니다.
- Console과 Network에 오류가 없으면 그때 폴더 이름을 정리합니다.
분리 전후의 화면과 동작이 같아야 첫 단계가 성공한 것입니다. 이 작업에서 새로운 기능까지 함께 넣으면 구조 변경으로 생긴 오류와 기능 오류가 섞입니다. 먼저 그대로 옮기고, 정상임을 확인한 뒤 다음 기능을 추가하세요.
분리한 파일이 연결되지 않을 때 확인할 순서
CSS가 전혀 적용되지 않으면 먼저 <link rel="stylesheet" href="styles.css">가 <head> 안에 있는지 봅니다. 파일 이름이 style.css인데 HTML에는 styles.css라고 적지 않았는지도 확인하세요. Network에 CSS 요청 자체가 없다면 연결 태그가 빠졌거나 문법이 깨진 경우이고, 요청은 있지만 404라면 경로 또는 파일 이름 문제일 가능성이 큽니다.
화면은 보이지만 버튼이 작동하지 않으면 Console의 첫 번째 오류부터 읽으세요. Cannot read properties of null처럼 화면 요소를 찾지 못했다는 오류가 보이면 선택자 철자와 실행 시점을 확인합니다. HTML에 id="start-button"이 있는데 JavaScript가 startButton을 찾고 있지는 않은지, defer 없이 문서 앞부분에서 스크립트를 실행하지는 않았는지 살펴보세요.
JavaScript 모듈에서만 실패하면 세 가지를 봅니다. 첫째, 가져오는 경로에 ./ 또는 ../가 빠지지 않았는지 확인합니다. 둘째, 내보낸 이름과 가져온 이름이 일치하는지 봅니다. 셋째, 파일을 더블클릭한 file: 주소가 아니라 로컬 서버의 http: 주소로 열었는지 확인하세요. 이 세 조건을 한 번에 바꾸지 말고 하나씩 고쳐 새로고침하면 실제 원인을 남길 수 있습니다.
이미지만 보이지 않을 때는 HTML과 CSS에서 경로 기준이 다를 수 있다는 점을 다시 확인합니다. HTML의 <img src="images/player.png">는 HTML 파일을 기준으로 하지만, styles/main.css 안의 url()은 그 CSS 파일을 기준으로 계산해요. 같은 그림인데 한 곳에서는 보이고 다른 곳에서는 사라진다면 이 차이가 원인인 경우가 많습니다.
검사가 끝나면 브라우저 한 종류에서만 보지 말고 창 너비를 줄여 버튼과 캔버스가 잘리지 않는지도 확인하세요. 키보드 입력만 있는 게임이라면 모바일에서 조작할 방법이 없는 것이 구조 오류는 아니지만, 공개할 결과물이라면 터치 대안이 필요한지 별도로 판단해야 합니다.
지금 선택할 수 있는 가장 단순한 기준
한 화면을 혼자 실험하고 있다면 한 파일로 시작하세요. 같은 스타일이나 기능을 다시 쓰기 시작했거나, 수정 위치를 찾는 시간이 코드를 쓰는 시간보다 길어졌다면 CSS와 JavaScript를 분리하면 됩니다. 기능별 JavaScript 모듈은 입력·점수·저장처럼 책임의 경계가 실제로 생긴 다음 단계예요.
오늘 당장 할 일은 폴더를 많이 만드는 것이 아닙니다. 현재 파일에서 CSS를 한 번, JavaScript를 한 번 따로 옮기고, 브라우저 개발자 도구에서 경로 오류가 없는지 확인해 보세요. 작은 프로젝트의 좋은 구조는 유명한 템플릿을 닮은 구조가 아니라, 다음 수정 위치를 망설이지 않고 찾을 수 있는 구조입니다.