"HTML 문자열을 어디에, 어떻게 넣을까" 정리해 두면
성능이랑 보안 둘 다 덜 털린다.
처음에 DOM 다룰 때는 거의 무조건 이렇게 시작한다.
target.innerHTML = "<p>뭐라도 나오게 해봐...</p>";
빠르고 편하다. 근데 규모가 커지면 갑자기 이런 고민이 튀어나온다.
- innerHTML += 로 계속 추가해도 되나?
- 특정 위치에만 쏙 끼워 넣고 싶을 땐 뭐 쓰지?
- XSS 같은 보안 이슈는 어떻게 피하지?
이번 글에서는 HTML 삽입/조작할 때 자주 쓰는 3가지 DOM 메서드를
내가 실제로 써본 기준으로 정리해 본다.
- innerHTML
- insertAdjacentHTML
- document.createRange().createContextualFragment
결론 먼저
한 줄씩만 꽂으면 이렇게 정리할 수 있다
- 전체 교체 → innerHTML로 간단하게
- 지정 위치 삽입 → insertAdjacentHTML로 효율적으로
- 보안 + 세밀 조작 → createContextualFragment로 안전하게
조금만 더 풀면
- innerHTML: 요소 안의 내용을 통째로 덮어쓰는 용도
- insertAdjacentHTML: 요소 앞/뒤/안쪽 앞/안쪽 뒤에 문자열로 HTML 삽입
- createContextualFragment: HTML 문자열을 실제 DOM Fragment로 파싱해서,
appendChild, insertBefore, replaceChild 같은 노드 단위 조작에 쓰기 좋음
1. innerHTML - 통으로 갈아엎을 때
역할
- 요소의 전체 HTML을 문자열로 통째로 교체
- += 로 쓰면 추가처럼 보이지만, 실제로는 전체를 다시 그린다
특징
- 직관적이고 코드가 짧다
- 하지만 내부적으로 전체 DOM을 재파싱하기 때문에
- 내용이 많을수록 CPU/메모리 부담이 커진다
- 이벤트 핸들러, 스크롤 상태 등도 날아갈 수 있다
예시
const target = document.getElementById("box");
// 전체 교체
target.innerHTML = "<p>새로운 내용</p>";
// 추가하는 것처럼 보이지만... 실제로는 전체를 다시 파싱한다
// 큰 영역에 반복해서 쓰면 성능이 서서히 망가질 수 있음
target.innerHTML += "<p>추가 내용</p>";
언제 쓸까?
- 초기 UI를 한 번에 렌더링할 때
- 간단한 실험/데모 코드
- "이 안쪽은 그냥 몽땅 갈아엎어도 된다"는 확신이 있을 때
실무에서 계속 innerHTML += 패턴으로 붙이다 보면
어느 순간 불필요한 재렌더링 때문에 성능이 미묘하게 느려지는 걸 체감하게 된다.
2. insertAdjacentHTML - 지정 위치에 문자열로 꽂기
역할
- 기존 DOM은 그대로 두고, 특정 위치에만 HTML 문자열을 삽입
- innerHTML보다 좀 더 정밀하게, 효율적으로 넣을 수 있다
시그니처
element.insertAdjacentHTML(position, htmlString);
position 옵션
- "beforebegin": 요소 바로 앞 (형제 노드)
- "afterbegin": 요소 안쪽 맨 앞 (첫 번째 자식)
- "beforeend": 요소 안쪽 맨 뒤 (마지막 자식)
- "afterend": 요소 바로 뒤 (형제 노드)
예시
const target = document.getElementById("box");
// 박스 안쪽 맨 앞에 삽입
target.insertAdjacentHTML("afterbegin", "<p>맨 앞 삽입</p>");
// 박스 안쪽 맨 뒤에 삽입
target.insertAdjacentHTML("beforeend", "<p>맨 뒤 삽입</p>");
// 박스 바로 앞에 수평선 추가
target.insertAdjacentHTML("beforebegin", "<hr>");
// 박스 바로 뒤에 수평선 추가
target.insertAdjacentHTML("afterend", "<hr>");
특징
- 기존 내용 전체를 건드리지 않고, 필요한 위치만 수정
- innerHTML보다 일반적으로 성능 측면에서 유리
- 하지만 여전히 HTML 문자열을 파싱해야 하고,
외부 입력을 그대로 넣으면 XSS 취약점이 생길 수 있다
언제 쓸까?
- 기존 DOM 구조는 유지하면서 앞/뒤/중간에만 살짝 끼워 넣고 싶을 때
- 리스트의 맨 앞/맨 뒤에 아이템을 추가하는 UI
- 토스트 메시지, 알림 배지처럼 특정 위치에 쓱 추가하는 UI
3. createContextualFragment - Fragment로 안전하게 다루기
이제 조금 더 "세밀하게" DOM을 만지고 싶을 때 쓰는 도구
역할
- HTML 문자열을 **DocumentFragment**로 파싱해 준다
- 이 Fragment를 가지고
- appendChild
- insertBefore
- replaceChild
- document.body.appendChild 등과 조합해서
노드 단위로 안전하게 조작할 수 있다
기본 사용 패턴
const target = document.getElementById("box");
const range = document.createRange();
const fragment = range.createContextualFragment("<p>파싱된 노드</p>");
// 맨 뒤에 삽입
target.appendChild(fragment);
예시: 여러 위치에 응용해 보기
const target = document.getElementById("box");
const range = document.createRange();
// 1. Fragment 만들기
let fragment = range.createContextualFragment("<p>파싱된 노드</p>");
// 맨 뒤 삽입
target.appendChild(fragment);
// 다시 쓰고 싶으면 Fragment를 새로 만들어야 한다
fragment = range.createContextualFragment("<p>다른 위치</p>");
// 맨 앞 삽입
target.insertBefore(fragment, target.firstChild);
// 특정 자식 앞에 삽입
const secondChild = target.children[1];
fragment = range.createContextualFragment("<p>특정 위치</p>");
target.insertBefore(fragment, secondChild);
// 노드 교체
const toReplace = target.querySelector("p");
fragment = range.createContextualFragment("<p>교체된 노드</p>");
target.replaceChild(fragment, toReplace);
// 다른 곳으로 옮기기
fragment = range.createContextualFragment("<p>다른 영역</p>");
document.body.appendChild(fragment);
특징
- 파싱 결과가 **Fragment(실제 DOM 노드 묶음)**라서
- 노드 단위 API와 자연스럽게 어울린다
- 기존 노드를 교체/이동/재사용하기 좋다
- <script> 실행이 기본적으로 되지 않아서
- 직접 innerHTML로 넣는 것보다 안전한 패턴을 만들 수 있다
- 다만 코드 양이 늘어나고,
- "간단히 한 줄로 때우고 싶다"는 욕구와는 거리가 있다 😅
언제 쓸까?
- 성능/보안을 조금 더 신경 써야 하는 UI
- 재사용 가능한 DOM 조각을 만들어서 이리저리 옮길 때
- 프레임워크 없이 커스텀 컴포넌트 시스템을 구축할 때
선택 가이드 (어떤 상황에 뭘 쓸까?)
| 목적 | 추천 메서드 | 성능/보안 관점 메모 |
| 요소 안 전체 내용을 갈아엎기 | innerHTML | 전체 재파싱 → 너무 자주 쓰면 성능 저하 |
| 기존 내용은 유지 + 특정 위치 삽입 | insertAdjacentHTML | 문자열 파싱 비용 있음, XSS 주의 |
| 세밀한 노드 조작 + 안전한 처리 | createRange().createContextualFragment | 코드 조금 복잡하지만, 유연하고 재사용에 좋음 |
내 체크리스트
실제로 코드 짤 때는 대충 이렇게 고른다.
- 진짜 그냥 통째로 바꿔도 되는 영역인가?
- → 그렇다면 innerHTML 한 방으로 끝내도 된다
- 단, innerHTML +=로 반복 추가하는 패턴은 지양
- 기존 DOM은 유지하면서 앞/뒤/중간에만 살짝 넣고 싶나?
- → insertAdjacentHTML
- 외부 입력(유저 입력, 서버 응답 텍스트 등)을 신뢰할 수 없나?
- → 가능하면 직접 문자열 합치기를 줄이고,
Fragment + DOM API 조합을 고려.
- → 가능하면 직접 문자열 합치기를 줄이고,
- 성능이 신경 쓰이기 시작하는 규모인가?
- → 큰 리스트를 매번 innerHTML로 갈아엎는 코드는 한 번쯤 리팩터링 타이밍.
- 앞으로 DOM 조작 패턴을 컴포넌트화하고 싶나?
- → Fragment + appendChild/insertBefore/replaceChild 조합이 훨씬 유리.
오늘 내용 한 줄씩 다시 정리
- 간단 교체: innerHTML로 빠르게 처리하되, += 패턴 남발은 피하자
- 효율 삽입: insertAdjacentHTML로 기존 DOM은 유지한 채 필요한 위치에만 꽂자
- 안전 조작: createContextualFragment + DOM 메서드 조합으로 세밀하게 다루자
결론은 하나다.
HTML을 "어떻게 넣을지"만 고민하지 말고,
어디까지 갈아엎을지, 성능/보안을 얼마나 챙길지 같이 고민하면
DOM 코드 퀄리티가 한 단계 올라간다.
'FrontEnd > JavaScript' 카테고리의 다른 글
| JavaScript 합성(Composition) - 상속보다 유연하게 객체 설계하기 (0) | 2025.09.09 |
|---|---|
| ES6 클래스 vs 생성자 함수 - 요즘 자바스크립트에서 뭘 써야 할까? (0) | 2025.09.09 |
| JavaScript Function.call / apply / bind - this (0) | 2025.09.09 |
| [실무 기록] 폼 실시간 공유 3편 – Yjs Awareness로 상대 커서,선택 상태 보여주기 (0) | 2025.09.08 |
| [실무 기록] 폼 실시간 공유 2편 - WebSocket 서버 구조랑 네임스페이스 적응기 (0) | 2025.09.08 |