솔직히 말하면, 저는 매년 "올해의 프론트엔드 트렌드" 류의 글을 별로 좋아하지 않습니다. 대부분 뜬구름 잡는 얘기들이거든요. 그런데 최근 눈에 걸린 세 가지 기술 변화는 조금 다릅니다. CSS 표준 레이어, 테마 시스템 아키텍처, 데이터 페칭 패턴—각각의 영역에서 "이거 왜 이렇게 구현하고 있었지?" 하는 의문이 생기게 만드는 변화들입니다. 그리고 이 세 가지는 사실 하나의 방향을 가리키고 있습니다. 레이어 분리와 사용자 체감 속도.
1. CSS가 드디어 난수를 품는다 — random()과 random-item()
CSS는 태생적으로 결정론적인 언어입니다. 같은 입력이면 항상 같은 출력. 지금까지 JavaScript 없이 요소마다 다른 회전값이나 간격을 주려면 인라인 스타일을 동적으로 주입하거나, CSS 커스텀 프로퍼티를 JS에서 건드리는 수밖에 없었습니다. 이 구조가 마음에 걸렸던 개발자라면 CSS Values and Units Module Level 5 스펙에 포함된 두 함수에 주목해야 합니다.
random(min, max)는 지정한 범위 내에서 임의의 값을 반환합니다. 세 번째 파라미터로 스텝을 지정할 수 있어서 소수점이 나오지 않도록 제어도 가능합니다—레이아웃에서 서브픽셀 이슈가 생기는 게 싫은 분들은 이 옵션이 구세주처럼 느껴질 겁니다. 더 주목할 부분은 값 공유 옵션입니다. 같은 엘리먼트의 width와 height에 동일한 랜덤값을 공유시켜 정사각형을 만들거나, 반대로 모든 엘리먼트에 같은 값을 전역으로 고정하는 fixed 키워드까지 스펙에 포함되어 있습니다.
random-item()은 범위 대신 이산값 목록에서 하나를 선택합니다. display, font-family처럼 숫자 범위로 표현할 수 없는 속성에 쓸 수 있다는 뜻입니다. dev.to의 알바로 몬토로(Alvaro Montoro)가 정리한 스펙 리뷰에 따르면, 현재 Safari 26.2 이상에서 random()의 부분 지원이 시작됐고, 나머지 브라우저는 아직 미지원 상태입니다.
이 함수가 중요한 이유는 단순히 "JS 없이 되네" 가 아닙니다. 레이아웃 관심사는 레이아웃 레이어(CSS)에서 처리해야 한다는 아키텍처 원칙의 구현입니다. 지금까지 우리가 JS에서 처리하던 시각적 랜덤성은 사실 CSS가 담아야 할 책임이었을지도 모릅니다. 다만 프로덕션 적용 전에 캐싱 동작과 브라우저 지원 범위를 반드시 확인해야 합니다—지금 당장 쓰기보다는 디자인 시스템 로드맵에 올려두는 단계입니다.
2. 테마 로더는 아키텍처 문제다 — 무-플리커 다크모드 설계
다크모드 구현을 prefers-color-scheme 미디어 쿼리 하나로 끝내는 분들 아직 많으시죠? 그런데 "사용자가 명시적으로 선택한 테마"와 "OS 설정 테마"가 충돌할 때, 혹은 탭을 여러 개 열었을 때 테마가 제각각이 될 때 어떻게 하실 건가요? dev.to에서 지안화앙(Zijian Huang)이 ChatGPT 4를 활용해 설계한 테마 로더 아키텍처는 이 문제를 꽤 깔끔하게 풀어냅니다.
핵심 설계 원칙은 세 가지입니다. 첫째, 코어 로직과 UI의 완전한 분리. ThemeManager 클래스는 프레임워크를 모르고, Angular나 React는 단지 이 API를 소비합니다. 둘째, 배포 후 수정 가능한 JSON 설정. 테마 목록을 코드가 아닌 /assets/themes/themes.config.json으로 관리하기 때문에 빌드 없이 운영 중 테마를 추가하거나 순서를 바꿀 수 있습니다. CDN 호스팅도 지원합니다. 셋째, 무-플리커 부트스트랩. index.html에 인라인으로 삽입하는 작은 스크립트가 프레임워크 초기화보다 먼저 localStorage에서 저장된 테마를 읽어 data-theme 어트리뷰트를 설정합니다. Figma에서 볼 때는 괜찮았는데 실제 구현하면 첫 렌더 순간 흰 화면이 반짝이는 그 FOUC 문제, 이 방식으로 원천 차단됩니다.
탭 간 동기화는 BroadcastChannel API로 처리하고, 지원하지 않는 환경에서는 storage 이벤트로 폴백합니다. 이미 로드된 테마를 재요청하지 않는 체크 로직도 포함되어 있습니다. PWA 환경에서 오프라인 상태에도 테마 전환이 동작해야 한다는 요구사항까지 서비스워커 캐싱 전략과 연계해 고려했습니다. 사용자 입장에서는 그냥 "테마 바뀌는 게 빠르네" 정도로 느끼겠지만, 이 아키텍처가 없으면 그 매끄러움은 구현되지 않습니다.
3. 로딩 스피너를 없애는 가장 우아한 방법 — React Query + Router Loader
사용자가 링크를 클릭하면 컴포넌트가 마운트되고, 데이터를 요청하고, 로딩 스피너가 돌고, 데이터가 오면 화면이 그려집니다. 이 순서를 당연하게 여기고 계셨나요? dev.to의 에드리소(Edriso)가 정리한 React Query와 React Router 로더의 조합은 이 순서 자체를 뒤집습니다.
queryClient.ensureQueryData()가 핵심입니다. React Router의 로더는 컴포넌트 마운트 이전에 실행되는데, 여기서 ensureQueryData를 호출하면 캐시에 데이터가 있으면 즉시 반환하고, 없으면 네트워크 요청 후 캐싱해 반환합니다. 컴포넌트가 실제로 마운트될 시점에는 useQuery가 캐시에서 데이터를 꺼내오기 때문에 로딩 상태 자체가 발생하지 않습니다. 패턴 자체는 단순하지만 효과는 명확합니다—사용자 입장에서는 페이지 전환이 즉각적으로 느껴집니다.
설계상 주의할 점이 있습니다. 로더에서 데이터를 직접 반환하는 것이 아니라 파라미터만 반환하고, 실제 데이터는 React Query 캐시에 둔다는 원칙입니다. 로더와 컴포넌트가 동일한 queryKey를 가진 쿼리 설정 함수를 공유하도록 구성하는 것이 이 패턴의 핵심입니다. 번들 사이즈나 의존성 측면에서는 이미 @tanstack/react-query와 react-router-dom을 함께 쓰고 있는 프로젝트라면 추가 비용이 없습니다.
시사점: 세 트렌드가 가리키는 공통 방향
세 가지 기술을 나란히 놓고 보면 패턴이 보입니다. 관심사의 레이어 분리, 그리고 사용자가 기다리는 순간의 최소화. CSS random()은 시각적 랜덤성이라는 레이아웃 관심사를 JS 레이어에서 CSS 레이어로 돌려보냅니다. 테마 로더는 UI 컴포넌트와 완전히 분리된 코어 로직으로 테마 관리를 독립시킵니다. React Query + Router Loader는 데이터 페칭 시점을 컴포넌트 생명주기 밖으로 꺼냅니다. 모두 같은 방향입니다.
이 세 가지를 당장 전부 적용할 필요는 없습니다. CSS random()은 Safari 외 브라우저 지원이 안정화될 때까지 폴리필 전략을 먼저 검토해야 합니다. 테마 로더 아키텍처는 이미 테마 관련 FOUC나 탭 동기화 버그를 겪은 팀에게 먼저 유효합니다. React Query 프리페칭은 네비게이션 전환 시 Lighthouse LCP 점수에 직접 영향을 주므로, Core Web Vitals 개선이 필요한 프로젝트라면 우선순위를 높게 잡아볼 만합니다.
결국 2026년 프론트엔드 UX의 경쟁은 "기능이 있느냐 없느냐"가 아니라 "그 기능을 얼마나 빠르고 끊김 없이 느끼게 하느냐"로 수렴하고 있습니다. 로딩 스피너 하나를 없애고, 테마 전환 시 반짝임 하나를 잡고, CSS 한 줄로 시각적 다양성을 구현하는 것—작아 보이지만 쌓이면 사용자가 느끼는 품질이 달라집니다. 1px 어긋남에 밤을 새우는 게 당연한 이유가 여기 있습니다.