개발자님, 이 9가지는 브라우저가 이미 해결해 줬습니다 (오버엔지니어링은 그만!)
2026. 4. 6.
개발자님, 이 9가지는 브라우저가 이미 해결해 줬습니다 (오버엔지니어링은 그만!)
코드와 일, 그리고 개발 철학에 대한 글을 쓰고 깊은 기술 다이빙을 즐기는 건 저의 오랜 취미입니다. 하지만 여러분은 아마도 아직 많은 분이 잘 모르는 멋진 기능 리스트를 더 좋아하실 거라는 걸 저도 잘 알고 있죠. 😉
최근 저는 컨퍼런스 준비와 씨름하고, 성능 문제와 싸우며 다가오는 연휴를 맞이할 준비를 하고 있습니다. 정신없이 바쁜 나날이지만, 그래도 기분 좋은 일도 있었어요. 개인적으로 기술 글 외에 일반 글쓰기도 참 좋아하는데요. 지난여름 제 삶에 꽤 큰 변화가 있었고, 그때 제 정신 건강을 지키기 위해 SF 소설을 쓰기 시작했습니다. 그걸 폴란드 SF 재단 공모전에 제출했는데, 비록 수상은 못 했지만 179개 출품작 중 13위까지 오르는 성과를 거뒀어요. 첫 시도였던 걸 생각하면 이 정도면 선방했죠! 😄
SF 이야기가 나온 김에, 요즘 우리 눈앞에서 펼쳐지는 '현실 SF'에 대한 이야기를 해볼까 합니다. 솔직히 저도 얼마 전까진 이런 기능들이 브라우저에 내장되어 있다는 걸 제대로 인지하지 못했거든요. 오늘 소개할 기능들 중 상당수는 아직 널리 알려지지 않았지만, 이미 대부분의 모던 브라우저에서 지원하고 있습니다. 재밌게 봐주세요!
1. "이건 좀 이따 실행하자" → requestIdleCallback
처음 이 API를 접했을 때, "뭐 이런 게 다 있나" 싶었습니다. 브라우저가 한가할 때 코드를 실행시켜 준다니, '그래… 멋지긴 한데, 이게 왜 중요할까?' 하는 의문이 들었죠.
알고 보니 사용 사례는 무궁무진합니다. 예를 들어, 페이지에서 사용자 행동 데이터를 수집하는 작업 같은 것 말이죠. 200개가 넘는 컴포넌트가 렌더링되고 있을 때 이런 걸 처리하고 싶지는 않을 겁니다. 😅 중요도가 낮은 데이터를 로딩하거나, 백그라운드에서 데이터를 전처리하고 이미지를 생성하는 작업 등도 좋은 예시가 됩니다.
솔직히 개발자 수만큼이나 다양한 사용 사례가 존재한다고 봐도 무방합니다. 제가 실무에서 웹사이트의 사용자 행동 분석 스크립트를 통합할 때, 초기 로딩 성능에 영향을 주지 않으려고 setTimeout으로 대충 미뤄두곤 했는데, 이 친구를 만나고 나서야 제대로 된 해결책을 찾았다는 안도감을 느꼈습니다.
function trackUserScrolling() {
console.log("User scrolled. This changes everything.");
}
if ("requestIdleCallback" in window) {
requestIdleCallback(trackUserScrolling);
} else {
// 폴백: 구형 브라우저를 위한 setTimeout
setTimeout(trackUserScrolling, 0);
}
지원: 모던 브라우저 (과거 Safari에서 누락된 적 있어 폴백은 여전히 좋은 선택입니다)
2. "내 입력창은 왜 하이라이트가 안 되지?" → :focus-within
포커스된 요소 자체를 스타일링하는 건 쉽습니다. 하지만 부모 div를 스타일링하고 싶다면 어떨까요? 예를 들어, 입력창에 포커스가 갔을 때 부모 div 배경을 핑크색으로 바꾸거나 꽃무늬를 넣고 싶다고 가정해 봅시다. JavaScript로 40줄짜리 코드를 작성할 수도 있겠지만… 그냥 :focus-within을 사용하면 됩니다.
작동 방식은 간단합니다. 이벤트 리스너도 필요 없고, 버그도 없고, 고통도 없습니다. 예전에는 이런 걸 구현하려고 JavaScript 이벤트 리스너를 덕지덕지 붙였던 기억이 나는데, :focus-within을 만나고 나서는 정말 세상이 편해졌죠.
.form-field {
border: 1px solid #ccc;
padding: 12px;
}
.form-field:focus-within {
border-color: hotpink; /* 자식 요소에 포커스가 있을 때 부모의 테두리 색상 변경 */
}
<div class="form-field">
<input placeholder="Type something meaningful..." />
</div>
지원: 대부분의 주요 브라우저에서 지원
3. "오프라인 모드를 보여줘야 해" → navigator.onLine
PWA(Progressive Web App)를 개발해 보신 적 있나요? 저는 여러 번 경험했는데, 그때마다 사용자가 연결을 잃었을 때(예를 들어, 야생에 있거나 엘리베이터에 갇혔을 때 😄) 어떻게 해야 할지가 영원한 문제였습니다. 복잡한 if 문을 잔뜩 작성할 수도 있지만, 그냥 offline과 online 이벤트를 리스닝하면 됩니다. offline일 때는 IndexedDB에 데이터를 저장하고, 사용자가 다시 online 상태가 되면 서버로 전송하면 되죠.
PWA 프로젝트에서 이 기능을 활용해 사용자가 잠시 네트워크가 끊겨도 데이터를 안전하게 저장하고 나중에 동기화하도록 구현했을 때, 정말 많은 개발 공수를 줄일 수 있었습니다. 특히 모바일 환경에서 불안정한 네트워크 상황을 고려하면 필수적인 기능이라고 봅니다.
window.addEventListener("offline", () => {
alert("You are offline. Time to panic.");
// IndexedDB에 데이터 저장 로직 추가
});
window.addEventListener("online", () => {
alert("You're back. Panic cancelled.");
// 저장된 데이터 서버로 전송 로직 추가
});
지원: 널리 지원됨 (하지만 "online" ≠ "백엔드가 작동한다"는 것을 기억하세요 😅)
4. "부드러운 애니메이션, 하지만 저주받은" → requestAnimationFrame
이런 코드는 다들 한 번쯤 보셨을 겁니다.
setInterval(() => {
element.style.left = Math.random() * 100 + "px";
}, 16);
이게 최선의 방법이 아니라는 건 느낌으로 알 수 있습니다. 😉 그냥 버벅거릴 뿐이죠. 다행히 우리는 requestAnimationFrame을 가지고 있습니다. 이 친구는 브라우저의 리페인트(repaint) 주기와 동기화되어 실제 애니메이션을 부드럽게 만들어 줍니다.
실무에서 복잡한 UI 애니메이션을 구현할 때, setInterval로 시작했다가 끊김 현상에 좌절하고 결국 requestAnimationFrame으로 갈아탔던 경험, 다들 한 번쯤 있으실 겁니다. 그 차이는 정말 명확하죠. 사용자 경험에 직접적으로 영향을 미치는 부분이기에, 애니메이션은 반드시 이 API로 구현해야 합니다.
function animate() {
element.style.transform = `translateX(${Date.now() % 300}px)`;
requestAnimationFrame(animate); // 다음 프레임에 다시 애니메이션 요청
}
requestAnimationFrame(animate); // 애니메이션 시작
지원: 모든 브라우저
5. "이 카드는 여기에서만 반응해야 해" → Container Queries
이 기능은 거의 '반칙'처럼 느껴집니다. 저는 경력상 더 이상 CSS를 거의 작성하지 않는 단계에 이르렀지만 (물론 "2026년에 CSS 학습은 시간 낭비일까?" 같은 글을 쓸 때는 예외지만요), 한때는 정말 많은 CSS를 짰습니다.
그때는 뷰포트 전체가 아닌 특정 요소에만 미디어 쿼리를 적용할 수 있었다면 얼마나 좋았을까, 하고 얼마나 간절히 바랐는지 모릅니다. 이제 드디어 그럴 수 있게 된 거죠! 컴포넌트가 스스로 환경을 인지하고 반응합니다. 이제 우리는 커피 한 잔 하러 가도 됩니다. 제가 한창 CSS에 매달려 있을 때 이 기능이 있었다면 야근이 절반은 줄었을 겁니다. 컴포넌트 단위의 반응형 디자인이 이렇게 깔끔하게 풀릴 줄이야, 정말 격세지감이죠.
.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);
버그는 이렇게 탄생합니다. 이건 마치 '알리익스프레스산 짝퉁 암호'처럼 보이고, 작동하는 듯하다가 결국 말썽을 일으키죠. 우선, 이 방식은 엔진 구현에 따라 달라지기 때문에 내부에서 정확히 무슨 일이 일어나는지 알 수 없습니다. 특정 패턴이 나타날 가능성도 충분하며, ID가 충분히 많아지면 중복을 피할 수 없게 됩니다.
다행히 이제 우리는 간단한 네이티브 솔루션을 가지고 있습니다. 만능 해결책은 아니지만, crypto.getRandomValues는 상당히 견고합니다. 훨씬 나은 엔트로피를 제공하고, 이상한 패턴도 없으며, 충돌 가능성을 극적으로 줄여줍니다. 브라우저가 알아서 제대로 처리해 주거든요. 예전에 사내 툴에서 임시 ID를 Math.random()으로 생성했다가 데이터 충돌로 골머리를 앓았던 적이 있어요. 그때 crypto API의 존재를 알았더라면 하는 후회가 밀려오죠. 보안과 신뢰성이 중요한 곳이라면 무조건 이 친구를 써야 합니다.
const bytes = new Uint8Array(8); // 8바이트 배열 생성
crypto.getRandomValues(bytes); // 암호학적으로 안전한 랜덤 값으로 채움
const id = Array.from(bytes)
.map(b => b.toString(16).padStart(2, "0")) // 각 바이트를 16진수로 변환
.join("");
console.log("Secure-ish ID:", id);
지원: 널리 지원됨
7. "모달이 필요해" → <dialog>
브라우저가 마침내 나서서 "그래, 여기 모달 줄게"라고 말해준 건 정말 반가운 일입니다. 😄 이제 사용자들이 그렇게나 좋아하는 다이얼로그 하나 띄우겠다고 12KB짜리 라이브러리를 설치할 필요가 없습니다. 이 <dialog>는 기본적으로 접근성도 좋으니, 그야말로 일석이조죠.
모달 하나 띄우겠다고 수십 KB짜리 라이브러리를 설치하거나, 스크린 리더 접근성까지 고려하며 직접 구현하던 고통스러운 시절이 있었죠. 이제는 <dialog> 태그 하나면 끝이라니, 개발자의 삶의 질이 수직 상승했습니다. 정말 필요한 곳에만 라이브러리를 쓰고, 브라우저가 제공하는 표준 기능들을 적극적으로 활용해야 하는 이유를 잘 보여주는 사례입니다.
<dialog id="modal">
<p>Are you sure you want to deploy on Friday?</p>
<button onclick="modal.close()">Cancel</button>
<button onclick="alert('Good luck 😬')">Deploy</button>
</dialog>
<button onclick="modal.showModal()">Open modal</button>
지원: 모던 브라우저
8. "음성 입력이 되면 좋겠는데..." → Speech API
음성 인식을 위해 벌써 transformers.js 같은 라이브러리를 설치하고 계신가요? 진정하세요. 알고 보니 브라우저에도 비슷한 기능이 있습니다. 음… 적어도 크로미움 기반 브라우저에서는 말이죠. 😄 따라서 사용자에게 Chrome, Edge 또는 이와 유사한 브라우저를 사용하도록 "권장"할 수 있다면 충분히 활용할 만합니다. 개인적으로는 프로덕션 환경에서의 사용은 아직 조심스럽지만, 데모용으로는 어떨까요?
개인적으로 사이드 프로젝트에서 음성 입력을 테스트해봤을 때, 생각보다 인식률이 좋아서 놀랐습니다. 데모용으로는 정말 강력한 무기가 될 수 있겠다 싶었죠. 다만, 프로덕션에 적용하기 전에 브라우저 호환성을 꼼꼼히 확인하는 건 필수입니다. 특히 사용자층의 브라우저 점유율을 고려하는 것이 중요하겠죠.
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
const recognition = new SpeechRecognition();
recognition.onresult = e => {
console.log("You said:", e.results[0][0].transcript);
};
recognition.start(); // 음성 인식 시작
} else {
console.warn("Speech Recognition API is not supported in this browser.");
}
지원: 주로 크로미움 기반 브라우저
9. "이 CSS가 터질까?" → @supports
이것은 고전적인 "내 컴퓨터에서는 되는데" 문제에 대한 현대적인 해결책입니다. 적어도 CSS에서는 말이죠. 😉 특정 CSS 속성이 레이아웃을 망가뜨릴지 추측할 필요가 없습니다. 그냥 @supports 안에 감싸면 됩니다. 작은 함정이 있다면, 지원이 매우 좋긴 하지만 '말 그대로' 모든 곳에서 지원되는 것은 아니기 때문에 아이러니하게도 @supports를 @supports에 사용할 수도 있다는 겁니다.
예전엔 특정 CSS 속성이 지원되는지 확인하려면 JavaScript로 User-Agent를 파싱하거나, 복잡한 꼼수를 써야만 했죠. @supports 덕분에 이젠 CSS 단에서 깔끔하게 폴백(fallback)을 처리할 수 있게 됐습니다. 마치 try-catch 블록이 CSS에 생긴 느낌이랄까요? 새로운 기능 도입 시 안정성을 확보하는 데 이보다 좋은 방법은 없습니다.
.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-06 06:18:35