아직도 직접 코딩하고 있다면 주목! 브라우저가 이미 다 해결한 9가지 오버엔지니어링 사례
2026. 4. 5.
아직도 직접 코딩하고 있다면 주목! 브라우저가 이미 다 해결한 9가지 오버엔지니어링 사례
코드와 일, 그리고 개발 철학에 대한 생각들을 정리하는 걸 즐겨 하는 10년 차 IT 실무자이자 테크 블로거입니다. 물론 깊이 있는 기술 분석도 좋아하죠. 하지만 제가 올리는, 아직 많은 분이 잘 모르는 '꿀 기능' 리스트를 여러분이 가장 좋아한다는 사실도 잘 알고 있습니다. 😉
요즘 저는 컨퍼런스 준비와 끈질긴 성능 문제와의 싸움, 그리고 다가오는 명절 휴가를 위한 작은 몸부림으로 정신없는 나날을 보내고 있습니다. 그러면서도 늘 "어떻게 하면 좀 더 효율적으로, 간결하게 코드를 짤 수 있을까?" 하는 고민을 멈추지 않죠. 이런저런 문제 해결을 위해 브라우저의 내부 동작을 파고들다 보면, 가끔 "와, 이걸 브라우저가 이미 해주고 있었네?" 하고 무릎을 탁 치게 되는 순간들이 있습니다.
오늘 준비한 내용은 바로 그런 순간들의 모음입니다. 얼마 전까지만 해도 제 머릿속에는 없었던, 하지만 이미 대부분의 모던 브라우저에서 잘 지원하는 놀라운 기능들이죠. "아, 괜히 직접 구현했었네!"라는 탄식이 절로 나올지도 모릅니다. 재미있게 봐주세요!
1. "이 코드는 나중에 실행해야지" → requestIdleCallback
처음 이 API를 접했을 땐 "굳이 왜?"라는 생각이 들었습니다. 쉽게 말해 브라우저가 한가할 때 특정 코드를 실행하게 해주는 기능인데, 이게 왜 필요할까 싶었죠.
하지만 실무에서 다양한 상황을 겪으면서 수많은 활용 사례를 발견했습니다. 예를 들어, 사용자의 페이지 행동 데이터를 수집하는 작업은 200개가 넘는 컴포넌트가 렌더링될 때 끼어들면 성능에 악영향을 줄 수 있습니다. 이런 비핵심적인 작업은 requestIdleCallback으로 미루는 게 현명하죠. 중요도가 낮은 데이터 로딩, 백그라운드 이미지 생성, 데이터 전처리 같은 작업에 안성맞춤입니다.
개발자의 수만큼이나 다양한 활용 사례가 존재한다고 봐도 무방합니다.
function trackUserScrolling() {
console.log("User scrolled. This changes everything.");
}
if ("requestIdleCallback" in window) {
requestIdleCallback(trackUserScrolling);
} else {
setTimeout(trackUserScrolling, 0); // 폴백 처리
}
지원: 모던 브라우저 (역사적으로 Safari에는 없었으므로, 폴백 처리는 여전히 좋은 습관입니다.)
2. "왜 내 입력창은 하이라이트가 안 되지???" → :focus-within
특정 요소에 포커스가 갔을 때 스타일을 적용하는 건 쉽습니다. 하지만 그 요소의 부모 div를 스타일링하고 싶다면 어떨까요? 예를 들어, 입력창이 활성화되면 부모 div 테두리를 핑크색으로 바꾸고 싶을 때 말이죠. 예전에는 수십 줄의 JavaScript를 짜거나 복잡한 CSS 핵을 사용하기도 했습니다. 하지만 이제는 :focus-within 하나로 간단히 해결됩니다.
별도의 이벤트 리스너도 필요 없고, 버그 걱정도 덜 수 있습니다. 깔끔하죠.
.form-field {
border: 1px solid #ccc;
padding: 12px;
}
.form-field:focus-within {
border-color: hotpink; /* 자식 요소에 포커스가 가면 부모 테두리가 핫핑크로! */
}
<div class="form-field">
<input placeholder="의미 있는 내용을 입력하세요..." />
</div>
지원: 사실상 모든 주요 브라우저
3. "오프라인 모드를 보여주자" → navigator.onLine
PWA(Progressive Web App)를 구축해본 경험이 있다면, 사용자가 인터넷 연결을 잃었을 때(예를 들어, 산속에 있거나 엘리베이터에 탔을 때) 어떻게 처리할지가 항상 골칫거리입니다. 복잡한 if 문을 덕지덕지 붙이거나, offline 및 online 이벤트를 리스닝하면 됩니다. offline일 때는 IndexedDB에 데이터를 저장하고, online으로 돌아오면 서버로 전송하는 식으로 깔끔하게 처리할 수 있죠.
window.addEventListener("offline", () => {
alert("네트워크 연결이 끊어졌습니다. 패닉할 시간입니다!");
});
window.addEventListener("online", () => {
alert("네트워크 연결이 돌아왔습니다. 패닉 취소!");
});
지원: 폭넓게 지원 (하지만 "온라인"이 "백엔드가 잘 동작함"을 의미하진 않습니다 😉)
4. "부드러운 애니메이션, 하지만 저주받은" → requestAnimationFrame
이런 코드는 모두 한 번쯤 봤을 겁니다.
setInterval(() => {
element.style.left = Math.random() * 100 + "px";
}, 16);
이 코드를 보면 왠지 모르게 불길한 예감이 들지 않나요? 😅 맞습니다, 딱 봐도 랙이 걸릴 것 같은 코드죠. 다행히 브라우저의 리페인트 주기와 동기화되어 실제 움직임을 매우 부드럽게 만들어주는 requestAnimationFrame이 있습니다.
function animate() {
element.style.transform = `translateX(${Date.now() % 300}px)`;
requestAnimationFrame(animate); // 다음 프레임에 애니메이션 재개 요청
}
requestAnimationFrame(animate); // 애니메이션 시작
지원: 모든 브라우저
5. "이 카드는 여기에서만 적응해야 해" → Container Queries
이 기능은 제가 경력의 어느 시점에서 CSS를 거의 작성하지 않게 되었을 때 나타나서, 마치 반칙처럼 느껴지기도 합니다. (물론 2026년에 CSS를 배우는 것이 시간 낭비일까요? 같은 글을 쓸 때처럼 가끔씩은 여전히 CSS와 씨름합니다만.)
하지만 제가 한창 CSS를 많이 쓰던 시절이 있었죠. 그때 뷰포트 전체가 아닌 특정 요소에만 미디어 쿼리를 적용할 수 있었다면 얼마나 좋았을까! 정말 간절히 바라던 기능이었습니다. 제가 실무에서 반응형 UI를 만들 때 이걸 얼마나 간절히 바랐는지 모릅니다. 예전에는 뷰포트 기준으로만 잡아야 해서 컴포넌트 내부에서 유연하게 대응하기가 정말 어려웠거든요. 이제는 드디어 가능합니다. 컴포넌트 자체가 '스스로를 인식'하게 되었고, 덕분에 개발자는 커피 한잔의 여유를 가질 수 있게 되었습니다.
.card-wrapper {
container-type: inline-size; /* 컨테이너 쿼리를 적용할 기준 지정 */
}
.card {
display: grid;
}
@container (min-width: 400px) { /* 부모 컨테이너 너비가 400px 이상일 때 */
.card {
grid-template-columns: 1fr 2fr;
}
}
지원: 모던 브라우저 (필요하다면 폴백 추가)
6. "랜덤 ID, 뭐가 문제겠어?" → crypto.getRandomValues
const id = Math.random().toString(36).slice(2);
버그는 이렇게 태어나는 겁니다. 얼핏 보면 "이만하면 됐지" 싶은 AliExpress표 암호화처럼 보이고, 동작하는 듯하다가 어느 순간 문제가 터지죠. 우선, 내부적으로 어떤 일이 일어나는지 정확히 알 수 없는 엔진 구현에 의존합니다. 특정 패턴이 나올 가능성도 충분하고, ID를 많이 생성할수록 중복이 발생할 확률은 기하급수적으로 늘어납니다.
다행히 이제는 간단하고 안전한 네이티브 솔루션이 있습니다. 완벽한 은 탄환은 아니지만, crypto.getRandomValues는 훨씬 뛰어난 엔트로피를 제공하며, 이상한 패턴 없이 충돌 가능성을 극적으로 줄여줍니다. 브라우저가 알아서 제대로 처리해주는 거죠.
const bytes = new Uint8Array(8);
crypto.getRandomValues(bytes); // 암호학적으로 안전한 난수 생성
const id = Array.from(bytes)
.map(b => b.toString(16).padStart(2, "0"))
.join("");
console.log("Secure-ish ID:", id);
지원: 폭넓게 지원
7. "모달이 필요해요" → <dialog>
솔직히 브라우저가 "알았어, 너희가 그렇게 원하는 모달 여기 있어!"라고 나서준 건 정말 기분 좋은 일입니다. 사용자들이 그렇게 좋아하는 단순한 다이얼로그 하나를 열기 위해 더 이상 12KB짜리 라이브러리를 설치할 필요가 없습니다. 이 <dialog> 요소는 기본적으로 접근성도 좋으니, 그야말로 일석이조죠. 제가 모달 관련 라이브러리의 무거운 번들 사이즈 때문에 씨름했던 지난날을 생각하면, 진작에 나왔어야 할 기능이라고 봅니다.
<dialog id="modal">
<p>금요일에 배포하는 게 확실합니까?</p>
<button onclick="modal.close()">취소</button>
<button onclick="alert('행운을 빕니다 😬')">배포</button>
</dialog>
<button onclick="modal.showModal()">모달 열기</button>
지원: 모던 브라우저
8. "음성 입력이 되면 멋질 텐데…" → Speech API
음성 인식을 위해 벌써부터 transformers.js 같은 라이브러리를 설치하고 계신가요? 잠시 진정하세요. 브라우저도 이 기능을 가지고 있습니다. 음… 적어도 Chromium 기반 브라우저는 말이죠. 😉 따라서 Chrome, Edge 또는 이와 유사한 브라우저 사용을 "권장"할 수 있다면 충분히 활용할 수 있습니다. 개인적으로는 아직 프로덕션 환경에 바로 적용하기엔 조심스럽지만, 데모 용도로는 충분히 매력적입니다.
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
const recognition = new SpeechRecognition();
recognition.onresult = e => {
console.log("당신이 말했습니다:", e.results[0][0].transcript);
};
recognition.start();
}
지원: 주로 Chromium 기반 브라우저
9. "이 CSS가 터질까?" → @supports
여기 "내 컴퓨터에서는 잘 되는데?"라는 고전적인 문제에 대한 현대적인 해결책이 있습니다(적어도 CSS에서는요). 더 이상 특정 CSS 속성이 레이아웃을 망가뜨릴지 추측할 필요가 없습니다. 그냥 @supports 안에 감싸세요. 작은 함정이 있다면, 지원 범위가 매우 넓긴 하지만 문자 그대로 모든 곳에서 지원되는 것은 아니라는 점입니다. 아이러니하게도, @supports 자체를 @supports로 감쌀 수도 있겠죠!
.card {
background: white;
}
@supports (backdrop-filter: blur(10px)) { /* backdrop-filter 지원 여부 확인 */
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.6);
}
}
지원: 매우 우수
⚠️ 오해는 금물입니다
물론 라이브러리는 훌륭합니다. 때로는 라이브러리가 정말로 필요할 때도 분명 있습니다. 하지만 간혹 브라우저가 이미 몇 년 전에 해결해놓은 기능을 위해 불필요한 의존성을 추가하고 있지는 않은지 돌아볼 필요가 있습니다. 무언가를 설치하기 전에 잠시 멈춰 서서 스스로에게 물어보세요 (아니면 구글에 검색해보세요): "혹시 브라우저가 나보다 더 똑똑한 해결책을 가지고 있는 건 아닐까?" 때로는 그 답이 '그렇다'일 때가 있습니다. 그리고 그 사실은… 전혀 문제 될 게 없죠. 😉
원문: https://dev.to/sylwia-lask/9-things-youre-overengineering-the-browser-already-solved-them-o99 수집일: 2026-04-05 05:55:12