웹 개발 데일리 브리핑 — 2026-10-21
Soshy··조회수 —

기준 시점: 2026-10-21 06:00 KST
조사 범위: 2026-10-20 06:00 ~ 2026-10-21 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
Cloudflare·VoidZero, Rust 기반 TypeScript 컴파일러 tsrs 공개 — 초기 실험에서 타입 검사 최대 8배 가속
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 발표 주체: Cloudflare, VoidZero
- 프로젝트: tsrs
- 안정화 단계: Experimental
- 원문: https://blog.cloudflare.com/typescript-rust-port/
- 저장소: https://github.com/voidzero-dev/tsrs
Cloudflare와 VoidZero가 TypeScript 컴파일러를 Rust로 이식한 tsrs 프로젝트를 공개했다.
이번 프로젝트는 Cloudflare, VoidZero, Owner.com의 Max Schwenk가 협력해 개발하고 있다. TypeScript 공식 팀과도 긴밀하게 협력하며, JavaScript 개발 도구 프로젝트인 Oxc의 일부로 육성하고 있다.
TypeScript는 JavaScript에 정적 타입 시스템을 추가한 언어다.
프론트엔드 개발에서는 React와 Next.js, Vue 등의 프로젝트에서 TypeScript를 널리 사용한다.
TypeScript의 타입 검사는 코드가 실행되기 전에 잘못된 타입 사용이나 인터페이스 불일치를 발견하는 데 도움이 된다.
하지만 프로젝트의 규모가 커질수록 타입 검사에 필요한 시간과 메모리도 증가할 수 있다.
예를 들어 수천 개의 TypeScript 파일이 포함된 Monorepo에서는 타입 검사만으로 상당한 빌드 시간이 소비될 수 있다.
개발 환경에서도 타입 검사가 느리면 IDE가 오류를 표시하거나 코드 변경을 분석하는 속도가 저하될 수 있다.
tsrs는 이러한 문제를 해결하기 위해 기존 TypeScript의 언어 의미를 유지하면서 컴파일러의 실행 성능을 개선하는 것을 목표로 한다.
TypeScript의 새로운 언어가 아닌 기존 컴파일러의 이식
이번 발표에서 Cloudflare가 강조한 부분은 TypeScript를 Fork하지 않는다는 점이다.
Fork는 기존 프로젝트의 코드를 복사한 뒤 별도의 방향으로 개발하는 방식을 뜻한다.
만약 TypeScript의 새로운 구현이 공식 TypeScript와 서로 다른 타입 검사 규칙을 사용한다면 개발자에게 문제가 발생할 수 있다.
예를 들어 공식 TypeScript에서는 정상적인 코드가 다른 컴파일러에서는 오류로 판단될 수 있다.
반대로 공식 TypeScript에서 오류가 발생하는 코드가 다른 컴파일러에서는 통과할 수도 있다.
이러한 차이가 누적되면 프로젝트가 어떤 컴파일러를 사용하는지에 따라 동작이 달라진다.
Cloudflare는 이를 피하기 위해 TypeScript 언어의 설계와 변경 방향은 공식 TypeScript 팀이 결정해야 한다고 명시했다.
tsrs는 해당 결정에 맞춰 기존 언어의 동작을 최대한 충실하게 재현하는 것을 목표로 한다.
따라서 이번 프로젝트는 TypeScript에 새로운 문법이나 타입 시스템을 도입하는 발표가 아니다.
동일한 TypeScript 코드를 더 빠르고 효율적으로 분석하기 위한 실행 엔진 개발에 가깝다.
기존 TypeScript 7.0과의 성능 비교
Cloudflare가 공개한 초기 Benchmark에서는 상당한 성능 개선이 나타났다.
| 비교 항목 | 발표된 결과 |
|---|---|
| 비교 대상 | TypeScript 7.0 |
| 타입 검사 속도 | 최대 8배 |
| 메모리 사용량 | 약 70% 감소 |
| 비교 조건 | 동일한 수의 타입 검사 Worker 사용 |
| 측정 주체 | Cloudflare·VoidZero 자체 측정 |
Cloudflare는 이번 비교에서 타입 검사 Worker의 수를 동일하게 설정했다고 설명했다.
이 조건이 중요한 이유는 병렬 실행에 사용할 수 있는 Worker 수가 달라지면 전체 처리 시간이 크게 달라질 수 있기 때문이다.
예를 들어 한 컴파일러는 Worker 두 개를 사용하고 다른 컴파일러는 여덟 개를 사용한다면 실행 시간이 다르더라도 컴파일러 자체의 효율성 차이로 단정하기 어렵다.
이번 비교는 이러한 차이를 통제하려고 했다.
그러나 최대 8배라는 수치는 모든 TypeScript 프로젝트에서 동일하게 재현되는 결과가 아니다.
프로젝트의 타입 구조와 파일 수, 하드웨어, Worker 설정 등에 따라 실제 성능 차이는 달라질 수 있다.
또한 이번 수치는 개발 프로젝트에서 공개한 자체 측정 결과다.
독립적인 제3자가 다양한 프로젝트를 대상으로 검증한 결과와는 구분해야 한다.
TypeScript 7.1 Beta와의 관계
흥미로운 점은 공식 TypeScript 컴파일러도 계속 개선되고 있다는 사실이다.
Cloudflare에 따르면 TypeScript 7.1 Beta는 TypeScript 7.0보다 일부 Benchmark에서 최대 2.5배 빠르다.
여기에는 tsrs에서 개발한 일부 최적화가 공식 TypeScript 구현으로 반영된 결과도 포함돼 있다.
즉 tsrs가 별도의 컴파일러를 개발하면서 공식 프로젝트와 경쟁하는 구조만을 지향하는 것은 아니다.
Rust 구현에서 발견한 성능 개선 방법을 공식 TypeScript에도 기여하려는 방향이다.
따라서 비교할 때는 두 가지를 구분해야 한다.
첫 번째는 tsrs와 TypeScript 7.0 사이의 성능 차이다.
두 번째는 tsrs와 최신 TypeScript 7.1 Beta 사이의 성능 차이다.
TypeScript 7.1 자체도 개선되고 있으므로 7.0을 기준으로 측정한 배율을 최신 공식 컴파일러와의 성능 차이로 그대로 해석해서는 안 된다.
기본 설정에서는 메모리 사용량이 달라질 수 있다
이번 발표에는 성능 수치를 해석할 때 주의해야 할 또 다른 조건이 있다.
Cloudflare는 동일한 Worker 수로 비교했을 때 메모리 사용량이 약 70% 감소했다고 설명했다.
하지만 tsrs의 기본 설정은 실행 환경의 하드웨어에 따라 적응적으로 조정된다.
이 설정에서는 더 많은 메모리를 사용하면서 실행 시간을 줄일 수 있다.
따라서 기본 설정으로 실행한 tsrs가 모든 상황에서 TypeScript 7.0보다 70% 적은 메모리를 사용한다고 단정할 수 없다.
동일한 병렬 실행 조건의 메모리 측정과 실제 기본 설정의 메모리 사용량은 구분해야 한다.
실제 프로젝트에서 성능을 비교한다면 실행 시간뿐 아니라 최대 메모리 사용량과 Worker 수도 함께 측정해야 한다.
Rust를 선택한 이유: WebAssembly
Cloudflare가 Rust를 선택한 이유는 실행 속도만이 아니다.
중요한 목표 가운데 하나는 WebAssembly(Wasm)를 이용한 실행 환경의 확장이다.
WebAssembly는 브라우저를 포함한 다양한 실행 환경에서 바이너리 코드를 실행할 수 있도록 하는 기술이다.
Rust는 WebAssembly와의 통합이 비교적 용이하다.
특히 메모리 관리 모델이 특징적이다.
Rust는 일반적으로 런타임 Garbage Collector에 의존하지 않는다.
반면 Go 프로그램을 WebAssembly로 컴파일할 때는 Go의 런타임과 Garbage Collector도 함께 포함될 수 있다.
이러한 차이는 제한된 메모리에서 컴파일러를 실행해야 하는 환경에 영향을 줄 수 있다.
예를 들어 브라우저 안에서 TypeScript 프로젝트를 분석하는 개발 도구를 생각할 수 있다.
서버에 모든 코드를 전송하지 않고 브라우저에서 타입 검사를 수행할 수 있다면 실행 환경을 더욱 독립적으로 구성할 수 있다.
Cloudflare Workers처럼 제한된 실행 환경에서도 비슷한 가능성이 존재한다.
Cloudflare는 이러한 구조를 이용해 브라우저와 서버리스 환경 안에서 완전한 개발 도구 체인을 실행하는 방향을 제시했다.
다만 현재 tsrs가 모든 브라우저에서 완전한 TypeScript 개발 환경을 제공한다는 의미는 아니다.
WebAssembly를 활용한 폭넓은 실행 환경은 프로젝트의 개발 목표이자 확장 가능성이다.
Oxc와의 통합
tsrs는 Oxc JavaScript 도구 체인과 연결된다.
Oxc는 Rust 기반 JavaScript 도구 프로젝트로, JavaScript와 TypeScript 코드의 파싱과 분석 등에 사용된다.
Cloudflare는 향후 tsrs를 Oxc 생태계의 다른 도구와 연결할 가능성도 언급했다.
대표적인 예가 Oxlint의 타입 정보를 활용하는 Lint 규칙이다.
일반적인 Lint는 코드의 문법과 구조를 분석해 잠재적인 문제를 찾는다.
하지만 일부 규칙은 변수와 함수의 실제 타입을 알아야 정확하게 판단할 수 있다.
예를 들어 Promise를 잘못 처리하는 코드나 타입에 따라 동작이 달라지는 API 사용을 검사하는 경우다.
이때 빠른 TypeScript 타입 검사 엔진을 활용하면 타입 정보를 필요로 하는 Lint 규칙의 실행 비용을 줄일 가능성이 있다.
그러나 해당 통합은 향후 검토하는 방향이다.
현재 tsrs를 설치했다고 해서 모든 Oxlint 규칙이 자동으로 타입 정보를 사용하게 되는 것은 아니다.
tsc-rs와 통합 계획
Cloudflare는 Theo Browne가 개발한 tsc-rs 프로젝트와 tsrs를 하나의 패키지로 통합하는 작업을 진행하고 있다고 설명했다.
이 통합의 목표에는 다음 기능이 포함된다.
- TypeScript의 주요 언어 기능
- Language Server Protocol(LSP)
- Content Mapper
- 기존 TypeScript와의 호환성 유지
LSP(Language Server Protocol)는 편집기와 언어 분석 서버가 통신하기 위한 프로토콜이다.
IDE에서 자동완성, 오류 표시, 정의로 이동, 참조 검색 같은 기능을 제공하는 데 사용된다.
따라서 컴파일러가 빠르다는 사실과 실제 IDE에서 동일한 개발 경험을 제공한다는 사실은 별개의 문제다.
코드를 빠르게 타입 검사하더라도 IDE에 필요한 기능이 구현되지 않았다면 기존 도구를 완전히 대체하기 어렵다.
Cloudflare는 이러한 기능을 포함해 공식 TypeScript와의 차이를 줄이는 방향으로 개발하고 있다.
현재 확인된 호환성 차이
현재 tsrs는 Experimental 단계다.
공식 발표에서는 TypeScript의 오류 메시지를 출력하는 순서 등 일부 차이가 존재한다고 명시했다.
타입 검사 결과의 순서는 사소하게 느껴질 수 있다.
하지만 기존 CI 시스템이나 개발 도구가 오류 출력 결과를 비교하는 경우에는 영향을 줄 수 있다.
또한 모든 TypeScript 기능과 편집기 연동 기능이 완성된 정식 컴파일러라고 볼 수 없다.
현재 공개된 Experimental 패키지는 npm에서 설치할 수 있다.
- 패키지:
tsrs - 실행 명령:
npx tsrs
그러나 운영 중인 프로젝트의 공식 TypeScript 컴파일러를 즉시 교체해야 한다는 의미는 아니다.
현재는 호환성과 성능을 검증하기 위한 실험적인 단계다.
이번 발표는 TypeScript 생태계에서 컴파일러의 실행 성능과 메모리 효율성, WebAssembly 기반 이식성을 동시에 개선하려는 프로젝트다.
특히 TypeScript 언어 자체를 변경하지 않으면서 개발 도구의 구현 언어와 아키텍처를 변경하는 접근이라는 점이 특징이다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
W3C, WHATWG, TC39, MDN, Chrome Developers·Chromium, Mozilla·Firefox, WebKit·Safari의 공식 발표 및 관련 개발 채널을 확인했다.
WebKit에서는 10월 20일부터 21일까지 Contributors Meeting이 진행되고 있다. CSS Anchor Positioning, Site Isolation, 메모리 안전성 등과 관련된 기술 발표가 예정돼 있지만, 행사 일정 자체를 새로운 브라우저 기능의 정식 출시로 취급하지 않았다.
WebMCP 관련 Community Group 규격 문서에도 10월 20일 날짜가 표시돼 있다. 다만 이 문서는 현재 개발 중인 규격의 초안이며, W3C의 정식 표준이 아니다. 문서의 날짜만으로 새로운 기능이 이날 정식 발표되거나 브라우저에 안정적으로 배포됐다고 판단할 수 없으므로 별도의 신규 기능 발표로 포함하지 않았다.
JavaScript 언어 표준과 CSS 기능에서도 이번 조사 기간에 정식 안정화가 확인된 주요 변경은 발견하지 못했다.
이전에 공개된 브라우저 기능을 활용한 실험이나 개발 빌드의 개별 변경은 이번 24시간의 새로운 공식 발표와 구분했다.
3️⃣ 백엔드·인프라
Vercel Functions, WebSocket 지원 정식 출시 — 서버리스 함수에서 양방향 실시간 연결 처리
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 안정화 단계: Generally Available
- 적용 대상: Vercel Functions, Fluid Compute
- 지원 요금제: 모든 Vercel 요금제
- 원문: https://vercel.com/changelog/websocket-support-is-now-generally-available
- 공식 문서: https://vercel.com/docs/functions/websockets
Vercel이 Functions에서 WebSocket 연결을 처리하는 기능을 정식 출시했다.
이번 기능은 2026년 6월 말 Public Beta로 공개됐으며, 약 4개월 동안의 운영을 거쳐 정식 출시됐다.
Vercel이 공개한 자체 운영 자료에 따르면 Beta 이후 Vercel CDN을 통해 처리된 WebSocket 연결은 누적 3억 건에 도달했다.
현재 처리하는 연결 수는 하루 약 1,500만 건이다.
이 수치는 Vercel 자체 인프라의 운영 통계이며, 다른 서버리스 플랫폼과 비교한 독립적인 성능 Benchmark는 아니다.
일반적인 HTTP 요청과 WebSocket의 차이
일반적인 HTTP 요청에서는 클라이언트가 서버에 요청을 보내고 서버가 응답을 반환한다.
예를 들어 사용자가 상품 목록을 열면 브라우저가 상품 데이터를 요청하고 서버는 JSON 응답을 반환한다.
이러한 방식은 요청과 응답이 명확하게 구분되는 작업에 적합하다.
하지만 실시간 채팅이나 공동 편집에서는 서버가 클라이언트에 새로운 정보를 즉시 전달해야 할 수 있다.
사용자가 매초 서버에 새로운 메시지가 있는지 확인하는 Polling 방식도 가능하지만 불필요한 요청이 발생할 수 있다.
WebSocket은 이러한 문제를 해결하기 위해 하나의 연결을 유지하면서 클라이언트와 서버가 양방향으로 메시지를 주고받도록 한다.
예를 들어 채팅방에서 새로운 메시지가 도착하면 서버가 이미 연결된 브라우저에 메시지를 전달할 수 있다.
이번 정식 출시로 Vercel Functions에서도 이러한 연결을 직접 처리할 수 있게 됐다.
WebSocket 연결의 실행 구조
WebSocket 연결은 처음에는 일반적인 HTTP GET 요청으로 시작한다.
클라이언트는 HTTP 헤더에 연결을 WebSocket으로 변경하기 위한 Upgrade 정보를 포함한다.
서버가 요청을 승인하면 연결이 WebSocket으로 전환된다.
Vercel에서는 이 전환이 완료되기 전에 기존 요청 처리 기능을 적용한다.
대표적으로 다음 기능이 포함된다.
- Routing Middleware
- Rewrite
- Firewall 규칙
- Rate Limit
즉 WebSocket 연결이라고 해서 Vercel의 기존 요청 처리와 보안 정책을 완전히 우회하는 것은 아니다.
다만 WebSocket으로 전환된 이후에는 동일한 HTTP 요청이 반복되는 것이 아니라 하나의 연결 안에서 메시지가 전달된다.
따라서 HTTP 요청 단위로 적용되는 Rate Limit과 실제 WebSocket 메시지 단위의 제한은 구분해야 한다.
예를 들어 연결 생성 요청에 Rate Limit이 적용된다고 해서 해당 연결 안에서 전송하는 모든 메시지의 빈도가 자동으로 제한되는 것은 아니다.
필요한 경우 애플리케이션에서 메시지 처리량과 사용자별 권한을 별도로 관리해야 한다.
하나의 연결은 하나의 Function 인스턴스에 유지된다
Vercel Functions에서 생성된 WebSocket 연결은 해당 연결을 수락한 Function 인스턴스에 고정된다.
연결이 유지되는 동안 클라이언트가 전송한 메시지는 같은 인스턴스로 전달된다.
Fluid Compute를 이용하면 하나의 Function 인스턴스에서 여러 WebSocket 연결을 처리할 수 있다.
하지만 새로운 연결이 반드시 같은 인스턴스에 전달되는 것은 아니다.
예를 들어 사용자 A와 사용자 B가 같은 채팅방에 접속했다고 가정해 보자.
사용자 A의 연결은 인스턴스 1에 생성될 수 있다.
사용자 B의 연결은 인스턴스 2에 생성될 수 있다.
이때 인스턴스 1의 메모리에만 채팅방 정보를 저장하면 인스턴스 2가 동일한 정보를 자동으로 공유하지는 않는다.
따라서 여러 인스턴스 사이에서 메시지를 전달하려면 외부 상태 저장소나 메시지 전달 시스템이 필요하다.
Vercel은 Redis를 예로 제시한다.
Redis의 Pub/Sub 기능을 이용하면 서로 다른 Function 인스턴스가 같은 채널의 메시지를 주고받도록 구성할 수 있다.
연결 종료와 재연결
WebSocket은 연결이 계속 유지되는 프로토콜이지만 무기한 유지되는 것은 아니다.
Vercel Functions에는 최대 실행 시간 제한이 존재한다.
Function이 최대 실행 시간에 도달하면 WebSocket 연결도 종료된다.
따라서 클라이언트에는 재연결 로직이 필요하다.
예를 들어 다음과 같은 과정을 처리할 수 있어야 한다.
- WebSocket 연결이 종료된다.
- 일정 시간 이후 연결을 다시 시도한다.
- 실패하면 재시도 간격을 늘린다.
- 연결에 성공하면 기존 채널을 다시 구독한다.
- 연결이 끊어진 동안 놓친 데이터를 다시 가져온다.
이때 Exponential Backoff를 사용할 수 있다.
Exponential Backoff는 연결에 반복적으로 실패할 때 재시도 간격을 점진적으로 늘리는 방식이다.
예를 들어 1초, 2초, 4초, 8초처럼 간격을 늘릴 수 있다.
다만 재연결이 성공했다고 해서 이전의 메시지 상태가 자동으로 복구되는 것은 아니다.
메시지 이력과 구독 상태를 어떻게 복구할지는 애플리케이션이 설계해야 한다.
배포 이후 기존 연결의 처리
새로운 버전의 애플리케이션을 배포할 때도 주의가 필요하다.
Vercel의 공식 문서에 따르면 새 배포 이후 새로운 연결은 새로운 버전의 Function으로 전달될 수 있다.
반면 이미 열려 있던 연결은 종료될 때까지 이전 버전의 Function에 유지될 수 있다.
따라서 같은 시점에 서로 다른 버전의 서버 코드가 실행될 가능성이 있다.
예를 들어 새로운 버전에서 WebSocket 메시지 구조를 변경했다면 이전 버전과의 호환성을 고려해야 한다.
새로운 클라이언트가 이전 서버와 통신하거나 이전 클라이언트가 새로운 서버와 통신하는 상황이 발생할 수 있기 때문이다.
메시지에 버전 정보를 포함하거나 일정 기간 이전 형식을 지원하는 방식으로 대응할 수 있다.
지원하는 프레임워크
Vercel은 다양한 서버 프레임워크의 WebSocket 구현을 지원한다.
대표적인 예시는 다음과 같다.
| 실행 환경 | WebSocket 연결 방식 |
|---|---|
| Node.js | ws, Express, Hono 등 |
| Socket.IO | WebSocket Transport 직접 사용 |
| Bun | Bun.serve()의 WebSocket API |
| Nitro·Nuxt | Nitro의 WebSocket 기능 |
| Next.js | @vercel/functions의 upgradeWebSocket() |
| Python | FastAPI, Django Channels, Flask-Sock 등 |
여기서 Next.js는 별도로 구분할 필요가 있다.
Vercel Functions의 WebSocket 지원이 정식 출시됐다고 해서 Next.js 자체가 WebSocket Upgrade를 처리하는 표준 Route Handler API를 제공하는 것은 아니다.
Vercel 공식 문서는 Next.js의 Route Handler에서 @vercel/functions의 upgradeWebSocket()을 사용하는 방법을 안내한다.
이 기능은 @vercel/functions 3.10.0 이상에서 사용할 수 있다.
따라서 일반적인 Node.js 서버에서 WebSocket을 구현하는 방식과 Next.js의 Route Handler에서 구현하는 방식에는 차이가 있다.
또한 Socket.IO를 사용하는 경우 HTTP Long Polling 대신 WebSocket Transport를 직접 사용하도록 클라이언트를 설정해야 한다.
비용과 사용량 제한
WebSocket 연결은 일반적인 Vercel Function Invocation과 동일한 요금 및 실행 제한을 따른다.
Vercel은 Active CPU 기반 과금 방식을 적용한다.
따라서 연결이 유지되는 동안 모든 시간이 CPU 사용 시간으로 계산되는 것은 아니다.
실제로 메시지를 처리하기 위해 CPU가 동작하는 시간이 Active CPU 사용량에 영향을 준다.
그러나 연결을 열어 두는 비용이 완전히 무료라는 의미는 아니다.
Function 사용량과 메모리, 데이터 전송량 등의 과금 요소가 존재한다.
WebSocket 연결에서 전송하는 데이터에도 관련 데이터 전송 요금이 적용될 수 있다.
또한 WebSocket을 사용하려면 Fluid Compute가 활성화돼 있어야 한다.
이번 발표는 새로운 WebSocket 프로토콜을 만든 것이 아니다.
기존 웹 표준인 WebSocket을 Vercel의 서버리스 Function 실행 환경에서 정식으로 지원하도록 안정화한 업데이트다.
Cloudflare Think, @cloudflare/computer 기반 Workspace로 전환 — 기존 Shell 사용자에게 데이터 이전이 필요한 Breaking Change
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare Think, Agent Skills
- 변경 유형: Breaking Change
- 원문: https://developers.cloudflare.com/changelog/post/2026-10-20-think-computer-workspace/
Cloudflare가 Think 에이전트와 Agent Skills의 작업 공간을 @cloudflare/computer 기반으로 변경했다.
이번 변경은 단순히 새로운 라이브러리를 추가한 것이 아니다.
기존 Workspace와 파일 저장 방식이 달라지기 때문에 이미 Think를 사용하는 프로젝트에서는 마이그레이션이 필요할 수 있다.
AI 에이전트는 하나의 요청을 처리하는 동안 여러 파일을 생성하고 수정할 수 있다.
예를 들어 코딩 에이전트가 프로젝트의 소스코드를 생성하거나, 설정 파일을 수정하거나, 분석 결과를 파일에 저장할 수 있다.
이때 에이전트에게 필요한 것은 단순한 함수 호출 인터페이스가 아니다.
파일을 저장하고 다시 읽을 수 있는 작업 공간과 코드를 실행할 수 있는 환경도 필요하다.
Cloudflare는 이러한 기능을 하나의 Workspace 구조로 제공하기 위해 @cloudflare/computer를 사용하도록 변경했다.
새로운 Workspace 구조
새로운 구조에서는 Workspace 객체를 생성하고 필요한 실행 Backend를 등록한다.
JavaScript 실행 환경에는 다음 Backend를 사용할 수 있다.
WorkerJavaScriptBackend
공식 예제에서는 이 Backend를 다음 모듈에서 가져온다.
@cloudflare/computer/backends/worker-javascript
Workspace에는 다음 정보가 전달된다.
- Durable Object의 Storage
- 사용할 실행 Backend
- 실행 환경을 구분하는 ID
- 필요한 Loader
예를 들어 하나의 에이전트가 JavaScript 코드를 실행해야 한다면 JavaScript Backend를 등록할 수 있다.
이후 해당 Workspace를 이용해 파일과 실행 환경을 관리한다.
이러한 구조는 파일 저장과 코드 실행을 개별 도구로 흩어 놓는 대신 공통 Workspace에 연결하는 방식이다.
기존 @cloudflare/shell 지원 중단
이번 업데이트에서 다음 기능은 Deprecated 상태로 변경됐다.
@cloudflare/shell- Think의
createExecuteTool
Cloudflare는 이 기능들을 더 이상 개발하거나 지원하지 않는다고 명시했다.
따라서 기존 프로젝트에서 해당 기능을 사용하고 있다면 새로운 Workspace 구조로 이전해야 한다.
다만 패키지가 Deprecated 상태라는 사실과 기존에 배포한 모든 애플리케이션이 즉시 실행되지 않는다는 사실은 구분해야 한다.
실제 영향은 현재 사용 중인 패키지 버전과 애플리케이션 구성에 따라 달라질 수 있다.
문제는 이후 업데이트와 유지보수에서 기존 방식의 지원을 기대하기 어렵다는 점이다.
파일 저장 구조가 변경되는 문제
가장 중요한 제한사항은 기존 Workspace 데이터가 자동으로 이전되지 않는다는 것이다.
@cloudflare/computer는 기존 @cloudflare/shell과 다른 테이블에 파일을 저장한다.
따라서 새로운 Workspace 구조를 적용하면 기존에 저장했던 파일을 찾지 못할 수 있다.
예를 들어 에이전트가 이전에 생성한 프로젝트 파일을 Durable Object Storage에 보관하고 있었다고 가정해 보자.
새로운 Workspace가 초기화되면 기존 저장 테이블을 자동으로 읽지 않는다.
따라서 에이전트는 기존 파일이 존재하지 않는 것처럼 동작할 수 있다.
하지만 실제로 파일이 삭제되는 것은 아니다.
Cloudflare는 이전 데이터가 기존 테이블에 그대로 남아 있다고 설명한다.
새로운 Workspace에서 해당 파일을 사용하려면 별도의 이전 작업이 필요하다.
마이그레이션 방식
Cloudflare는 기존 Workspace 파일을 새로운 저장 형식으로 복사하는 마이그레이션 스크립트를 제공한다.
공식 안내에서는 기존 데이터를 유지하려면 이 스크립트를 사용하도록 권장한다.
반대로 기존 파일이 필요하지 않다면 새로운 빈 Workspace로 시작할 수도 있다.
그러나 운영 중인 AI 에이전트에서 이러한 선택을 할 때는 주의해야 한다.
에이전트가 저장한 파일이 단순한 임시 데이터인지, 이후 작업을 수행하는 데 필요한 영속적인 상태인지 확인해야 하기 때문이다.
예를 들어 다음 데이터는 성격이 다르다.
- 다시 생성할 수 있는 임시 코드
- 외부 API에서 다시 가져올 수 있는 캐시
- 사용자가 직접 편집한 프로젝트 파일
- 복구하기 어려운 분석 결과
마지막 두 종류의 데이터는 마이그레이션 과정에서 누락되지 않도록 검증할 필요가 있다.
또한 새 Workspace에서 파일을 정상적으로 읽을 수 있는지 확인하기 전까지 기존 데이터를 유지하는 편이 안전하다.
이번 업데이트는 모든 Cloudflare Worker의 파일시스템 동작을 변경하는 것이 아니다.
Think와 Agent Skills에서 사용하는 Workspace 구현을 새로운 라이브러리로 통합하면서 기존 사용자에게 데이터 이전을 요구하는 변경이다.
4️⃣ 개발 도구 및 보안
GitHub Package Firewall 공개 Preview — npm 패키지 설치 전에 악성 패키지와 공급망 위험 검사
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 안정화 단계: Public Preview
- 적용 대상: GitHub Enterprise Cloud
- 지원 패키지 생태계: npm
- 원문: https://github.blog/changelog/2026-10-20-github-package-firewall-in-public-preview/
GitHub가 패키지 설치 과정에서 악성 패키지와 공급망 위험을 검사하는 Package Firewall을 공개했다.
JavaScript 프로젝트에서는 npm을 통해 다양한 라이브러리를 설치한다.
예를 들어 React 애플리케이션을 개발할 때 UI 라이브러리와 상태 관리 도구, 빌드 도구 등을 설치할 수 있다.
문제는 패키지를 설치하는 행위 자체가 보안 위험이 될 수 있다는 점이다.
npm 패키지는 설치 과정에서 특정 Script를 실행하도록 구성할 수 있다.
대표적으로 다음 Script가 있다.
preinstallinstallpostinstall
이러한 Script는 정상적인 패키지에서도 사용될 수 있다.
하지만 공격자가 악성 패키지를 배포한다면 설치 과정에서 개발자 컴퓨터나 CI Runner의 환경 정보를 읽으려고 시도할 수 있다.
예를 들어 CI 환경 변수에 저장된 인증 토큰이나 클라우드 Credential이 공격 대상이 될 수 있다.
이 경우 개발자가 패키지를 실제로 Import하거나 애플리케이션에서 실행하지 않았더라도 피해가 발생할 가능성이 있다.
GitHub Package Firewall은 이러한 문제에 대응하기 위해 패키지가 실제로 설치되기 전에 정책을 검사하는 구조를 제공한다.
패키지 저장소와 개발 환경 사이에 Proxy 배치
Package Firewall은 개발 환경과 실제 패키지 저장소 사이에서 동작한다.
일반적인 npm 설치 흐름은 다음과 같다.
개발 환경 → npm Registry → 패키지 다운로드
Package Firewall을 사용하면 다음과 같이 변경된다.
개발 환경 → GitHub Package Firewall → npm Registry
개발 환경에는 다음과 같은 시스템이 포함될 수 있다.
- 개발자의 로컬 컴퓨터
- GitHub Actions Runner
- 다른 CI/CD 실행 환경
- AI 코딩 에이전트의 실행 환경
중요한 점은 Package Firewall이 메타데이터 요청과 실제 패키지 다운로드를 모두 검사한다는 사실이다.
패키지의 이름과 버전 목록만 확인하는 것이 아니다.
실제로 다운로드하는 패키지 데이터도 정책 평가의 대상이 된다.
정책에 포함되는 검사 항목
현재 제공되는 주요 정책은 다음과 같다.
| 정책 | 설명 |
|---|---|
| Cooldown Period | 새로 게시된 패키지를 일정 기간 동안 제한 |
| Malware Advisory | 알려진 악성 패키지 정보 검사 |
| Vulnerability Advisory | 알려진 취약점 정보 검사 |
| Attestation Downgrade | 공급망 증명 정보가 약화되는 변경 검사 |
Cooldown Period는 패키지가 새로 게시된 직후 바로 설치하지 않도록 하는 정책이다.
예를 들어 조직이 새 패키지 버전의 배포 이후 일정 기간이 지나야 설치하도록 제한할 수 있다.
이 방식은 악성 패키지가 배포된 직후 대량으로 설치되는 상황의 위험을 줄이는 데 도움이 될 수 있다.
하지만 새 패키지가 일정 기간 동안 공개돼 있었다는 사실이 안전성을 보장하지는 않는다.
오랫동안 유지된 악성 패키지도 존재할 수 있기 때문이다.
따라서 Cooldown Period만으로 공급망 보안을 완성할 수는 없다.
GitHub Advisory Database 연동
Package Firewall은 GitHub Advisory Database를 이용해 알려진 악성 패키지와 취약점 정보를 검사할 수 있다.
GitHub Advisory Database는 소프트웨어 패키지의 보안 취약점과 관련된 정보를 제공한다.
예를 들어 설치하려는 패키지 버전에 알려진 심각한 취약점이 있다면 조직의 정책에 따라 설치를 차단할 수 있다.
다만 Advisory Database에 등록되지 않은 악성 패키지는 해당 정보만으로 탐지할 수 없다.
따라서 알려진 취약점 검사와 실제 악성 행위 탐지는 구분해야 한다.
이번 기능이 모든 새로운 공급망 공격을 자동으로 탐지한다는 의미는 아니다.
Attestation Downgrade 검사
Attestation은 특정 소프트웨어 아티팩트가 어떤 과정을 거쳐 생성됐는지 증명하는 데 사용하는 정보다.
예를 들어 패키지가 신뢰할 수 있는 빌드 환경에서 생성됐는지 확인하는 데 활용할 수 있다.
공급망 공격에서는 패키지 자체뿐 아니라 해당 패키지가 생성된 과정의 신뢰성도 중요하다.
GitHub는 이번 Package Firewall에서 Attestation 수준이 낮아지는 변경을 검사하는 정책을 제공한다.
예를 들어 기존 패키지 버전에는 신뢰할 수 있는 증명 정보가 존재했지만 새로운 버전에는 해당 정보가 누락됐다고 가정할 수 있다.
이러한 변화가 항상 공격을 의미하는 것은 아니다.
패키지 제공업체의 배포 과정이 정상적으로 변경됐을 가능성도 있다.
하지만 신뢰할 수 있던 배포 과정이 갑자기 변경됐다면 검토할 필요가 있다.
Package Firewall은 이러한 상황을 정책 검사에 활용할 수 있도록 한다.
잘못 차단된 패키지의 예외 처리
보안 정책은 정상적인 패키지를 잘못 차단할 수 있다.
예를 들어 조직의 Cooldown Period에 따라 새로 출시된 보안 패치가 일시적으로 차단될 수 있다.
이 경우 오히려 중요한 취약점 수정을 적용하지 못하는 문제가 생길 수 있다.
GitHub는 이를 고려해 패키지 차단에 대한 예외 요청 절차를 제공한다.
사용자는 잘못 차단된 것으로 판단되는 패키지에 대해 예외를 요청할 수 있다.
관리자는 조직의 정책에 따라 요청을 검토할 수 있다.
다만 예외를 승인하면 해당 패키지에 적용되던 특정 보호 정책이 우회될 수 있으므로 실제 패키지의 위험을 함께 검토해야 한다.
모든 npm 설치가 자동으로 검사되는 것은 아니다
이번 기능에는 중요한 적용 조건이 있다.
Package Firewall을 활성화했다고 해서 조직의 모든 개발 환경에서 패키지 설치 요청이 자동으로 해당 서비스를 통과하는 것은 아니다.
관리자는 먼저 Enterprise 정책을 구성해야 한다.
이후 실제 개발 환경이나 CI Runner가 Package Firewall을 패키지 Registry 경로로 사용하도록 설정해야 한다.
예를 들어 어떤 CI 환경은 Package Firewall을 사용하지만 다른 개발 환경은 기존 npm Registry에 직접 연결할 수 있다.
이 경우 두 환경에 적용되는 보안 정책이 달라진다.
따라서 실제로 정책을 강제하려면 패키지 다운로드 경로도 함께 관리해야 한다.
지원 범위와 과금
현재 Package Firewall은 Public Preview 단계다.
지원 대상은 다음과 같다.
- GitHub Enterprise Cloud
- GitHub Enterprise Cloud with Data Residency
현재는 npm 패키지만 지원한다.
Maven, PyPI, Cargo 등 다른 패키지 생태계까지 동일한 기능을 제공한다고 해석해서는 안 된다.
Preview 기간에는 무료로 제공된다.
GitHub는 정식 출시 이후 사용량 기반 과금을 적용할 계획이라고 밝혔다.
따라서 장기적인 운영 비용은 정식 출시 이후의 요금 정책을 확인해야 한다.
이번 발표는 패키지를 설치한 이후 취약점을 발견하는 방식에서 나아가 설치 요청 자체를 정책에 따라 검사하고 차단하는 공급망 보안 기능을 추가한 변화다.
GitHub Secret Scanning, Base64 인코딩 토큰 대응 확대 — 11월에는 감사 로그의 Enterprise 식별값 변경 예정
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 적용 대상: GitHub Secret Scanning, Audit Log, 관련 Webhook·REST API
- 원문: https://github.blog/changelog/2026-10-20-updates-to-github-secret-scanning/
GitHub가 Secret Scanning의 인증 정보 탐지와 처리 기능을 개선하고, Enterprise Audit Log의 일부 필드 변경을 예고했다.
Secret Scanning은 Repository에 포함된 API Key와 Access Token 등의 민감한 인증 정보를 탐지하는 기능이다.
개발자가 실수로 Credential을 Commit하면 해당 정보가 Git 기록에 남을 수 있다.
특히 공개 저장소에 인증 정보가 업로드되면 외부 공격자가 이를 이용할 가능성이 있다.
비공개 저장소에서도 인증 정보 노출은 위험할 수 있다.
저장소에 접근할 수 있는 사용자가 많거나 다른 시스템과 코드가 공유될 수 있기 때문이다.
이번 업데이트에는 여러 개선이 포함됐지만, 특히 인코딩된 인증 정보의 처리와 감사 로그의 호환성 변경이 중요하다.
Base64로 인코딩된 GitHub Token 대응
GitHub는 Base64로 인코딩된 GitHub Token을 처리하는 기능을 확장했다.
Base64는 바이너리 데이터를 문자로 표현하기 위한 인코딩 방식이다.
암호화가 아니다.
따라서 Base64로 인코딩된 문자열은 누구나 다시 원래 데이터로 변환할 수 있다.
하지만 기존의 Secret Scanning에서는 인증 정보가 원래 형태가 아닌 인코딩된 문자열로 저장돼 있을 때 탐지가 어려울 수 있었다.
이번 변경으로 GitHub는 인코딩된 인증 정보를 디코딩하고 기존의 Token 폐기 기능과 연결할 수 있도록 했다.
지원 대상은 다음과 같다.
- Classic Personal Access Token
- Fine-grained Personal Access Token
- OAuth Access Token
- GitHub App User Access Token
적용 환경은 Secret Scanning이 활성화된 github.com의 비공개 및 내부 저장소다.
관련 Token Revocation 기능은 Public Preview 단계다.
따라서 모든 종류의 인코딩된 인증 정보가 모든 저장소에서 자동으로 폐기된다는 의미는 아니다.
또한 Base64 이외의 임의의 인코딩이나 변환 방식까지 모두 탐지한다고 가정해서는 안 된다.
인증 정보의 인코딩과 보안
이번 변경은 인증 정보를 Base64로 저장하는 방식이 안전하다는 의미가 아니다.
Base64는 데이터를 숨기는 보안 기능이 아니다.
예를 들어 API Key를 Base64로 변환해 소스코드에 포함해도 해당 Key가 외부에 노출될 위험은 그대로 존재한다.
정상적인 프로젝트에서는 인증 정보를 소스코드와 분리된 환경 변수나 비밀 정보 저장소에서 관리해야 한다.
이미 인증 정보가 노출됐다면 원본 문자열을 Git 기록에서 제거하는 것만으로 충분하지 않을 수 있다.
해당 Credential을 폐기하거나 교체하는 과정도 필요하다.
11월 12일 예정된 Audit Log 변경
이번 발표에는 2026년 11월 12일 적용 예정인 감사 로그 필드 변경도 포함됐다.
영향받는 이벤트는 다음과 같다.
secret_scanning_alert.*secret_scanning_push_protection.bypass
이 이벤트의 business 필드가 변경된다.
기존에는 Enterprise의 Display Name이 포함됐다.
변경 이후에는 Enterprise Slug가 포함될 예정이다.
Display Name은 사용자에게 표시되는 이름이다.
반면 Slug는 URL이나 시스템 내부 식별에 사용하는 문자열이다.
예를 들어 Enterprise의 표시 이름에 공백이 포함돼 있거나 이름이 변경된 경우 두 값이 다를 수 있다.
따라서 기존 감사 로그를 Display Name 기준으로 검색하거나 필터링하는 자동화 시스템은 변경 이후 정상적으로 동작하지 않을 가능성이 있다.
GitHub는 다음 방법을 안내한다.
- 기존 Display Name과 새로운 Slug를 모두 허용한다.
- 가능하다면 변경되지 않는
business_id를 사용한다.
이번 변경에서 business 필드의 자료형은 그대로 문자열이다.
이벤트 이름도 변경되지 않는다.
숫자형 business_id 역시 기존 값을 유지한다.
따라서 영향 범위는 해당 필드의 값에 의존하는 자동화 로직이다.
Webhook과 Custom Pattern 개선
Secret Scanning 관련 Webhook에도 변경이 있다.
예외 요청을 처리하는 Payload에는 다음 필드가 포함된다.
exemption_request.number
이는 Repository 안에서 사용하는 요청 번호다.
따라서 외부 시스템에서 해당 요청을 식별할 때 Repository 정보와 함께 사용해야 한다.
요청 번호 하나만으로 모든 Repository에서 유일한 요청을 식별할 수 있다고 가정해서는 안 된다.
Custom Pattern에도 변경이 이루어졌다.
기존에 생성된 열린 Alert가 수정된 Custom Pattern과 계속 일치하는 경우 해당 Alert를 불필요하게 종료했다가 다시 열지 않도록 개선했다.
또한 잘못된 Custom Pattern 인자가 전달됐을 때 서버 오류인 HTTP 500 대신 요청 데이터 오류를 나타내는 HTTP 422를 반환하도록 수정했다.
이러한 변경은 외부 자동화 시스템이 실제 서버 장애와 잘못된 요청을 구분하는 데 도움이 된다.
접근성 개선
이번 업데이트에는 Secret Scanning UI의 접근성 개선도 포함됐다.
예외 요청의 Request pending 상태는 별도의 동작을 실행하지 않고도 키보드로 포커스를 이동할 수 있도록 변경됐다.
또한 Token 폐기 과정의 진행 상태를 스크린 리더에 알리고, 완료 화면이 표시되면 해당 영역으로 포커스를 이동하도록 개선했다.
이는 보안 관리 화면 역시 키보드 사용자와 보조 기술 사용자가 정확하게 조작할 수 있어야 한다는 점과 관련된다.
이번 발표는 하나의 대형 신규 기능보다 기존 Secret Scanning의 탐지 범위와 API 동작, 접근성을 개선하면서 향후 감사 로그 호환성 변경을 예고한 업데이트다.
GitHub Dependabot, 취약한 의존성의 실제 버전을 REST API에서 제공 — npm·pnpm·Bun 최신 버전 지원도 확대
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 적용 대상: GitHub Dependabot, Dependabot Alerts REST API
- 버전 정보 원문: https://github.blog/changelog/2026-10-20-dependabot-alerts-rest-api-now-includes-dependency-versions/
- 패키지 매니저 지원 원문: https://github.blog/changelog/2026-10-20-dependabot-now-supports-npm-12-pnpm-11-and-12-and-bun-1-4/
GitHub가 Dependabot과 관련해 두 가지 업데이트를 공개했다.
첫 번째는 취약한 의존성의 실제 버전 정보를 REST API에 포함하는 변경이다.
두 번째는 npm과 pnpm, Bun의 최신 버전에 대한 Dependabot 지원 확대다.
두 변경은 서로 다른 기능이지만 모두 JavaScript 프로젝트의 의존성 관리와 관련되므로 함께 정리한다.
기존 Dependabot Alert에서 확인하기 어려웠던 정보
Dependabot은 Repository의 의존성을 분석하고 알려진 취약점이 발견되면 Alert를 생성한다.
예를 들어 프로젝트가 특정 npm 패키지를 사용한다고 가정해 보자.
해당 패키지의 1.0.0부터 1.5.0까지의 버전에 취약점이 존재한다면 Dependabot은 관련 Alert를 생성할 수 있다.
기존 REST API 응답에는 취약점이 영향을 주는 버전 범위가 포함돼 있었다.
하지만 Repository에서 실제로 어떤 버전을 사용하는지에 관한 정보는 직접 제공되지 않았다.
따라서 외부 보안 관리 시스템에서는 추가 작업이 필요할 수 있었다.
예를 들어 다음과 같은 방식이다.
- Dependabot Alert를 조회한다.
- 영향을 받는 패키지를 확인한다.
- Repository의 Lockfile을 가져온다.
- 실제 설치 버전을 분석한다.
- 취약점 버전 범위와 비교한다.
또는 GraphQL API의 다른 필드를 조회해야 했다.
이번 업데이트는 REST API 응답에서 실제 의존성 버전 정보를 직접 확인할 수 있도록 변경했다.
version_requirement 필드
새로운 필드는 다음과 같다.
dependency.version_requirement
해당 필드는 Dependabot이 마지막으로 기록한 의존성 버전 정보를 제공한다.
다만 반환하는 값은 의존성을 발견한 방법에 따라 달라진다.
| 의존성 탐지 방식 | 반환 정보 |
|---|---|
| Lockfile 분석 | 실제로 해결된 패키지 버전 |
| Manifest만 존재 | Manifest에 선언된 버전 또는 버전 범위 |
| Dependency Submission API | 빌드 도구가 제출한 실제 버전 |
예를 들어 package.json에 다음과 같은 버전 범위가 존재할 수 있다.
^2.1.0
하지만 pnpm-lock.yaml이나 package-lock.json에는 실제 설치된 버전이 기록된다.
예를 들어 Lockfile에서 2.1.5 버전으로 해결됐다고 가정해 보자.
Dependabot이 Lockfile을 기준으로 취약한 의존성을 발견했다면 새 필드에는 실제로 기록한 버전이 포함될 수 있다.
반면 Lockfile이 없는 경우에는 Manifest에 선언된 범위가 반환될 수 있다.
따라서 version_requirement를 모든 환경에서 실제 설치된 단일 버전이라고 해석해서는 안 된다.
지원하는 API와 Webhook
새 필드는 다음 응답에 포함된다.
- 개별 Dependabot Alert 조회
- Repository 단위 Alert 목록
- Organization 단위 Alert 목록
- Enterprise 단위 Alert 목록
dependabot_alertWebhook Payload
이는 Dependabot Alert를 별도의 보안 대시보드나 내부 관리 시스템과 연결하는 경우에 유용하다.
예를 들어 외부 시스템이 취약점별로 어떤 프로젝트에 영향을 주는지 집계한다고 가정해 보자.
기존에는 추가적인 Lockfile 분석이 필요했을 수 있다.
이번 변경으로 일부 정보를 Alert 응답에서 직접 얻을 수 있다.
반환값의 제한사항
GitHub는 새 필드의 제한사항도 명시했다.
첫 번째로 값이 없을 수 있다.
이 경우 null이 반환된다.
두 번째로 하나의 Alert가 여러 취약한 버전과 관련된 경우에는 기록된 값 하나만 반환될 수 있다.
따라서 모든 영향을 받는 버전을 나타내지 못할 수 있다.
세 번째로 긴 버전 범위는 일부가 잘릴 수 있다.
또한 반환값은 Dependabot이 마지막으로 기록한 정보다.
따라서 Repository의 파일이 변경된 직후라면 실제 최신 상태와 일치하는지 추가로 확인해야 할 수 있다.
npm 12·pnpm 11/12·Bun 1.4 지원
Dependabot은 다음 패키지 매니저 버전을 새롭게 지원한다.
- npm 12
- pnpm 11
- pnpm 12
- Bun 1.4
새로운 버전의 패키지 매니저를 사용하는 프로젝트에서도 Dependabot의 의존성 업데이트 기능을 계속 사용할 수 있도록 한 변경이다.
이는 프론트엔드 프로젝트의 패키지 매니저를 업데이트할 때 고려할 수 있는 요소다.
예를 들어 빌드 도구나 런타임을 새로운 버전으로 변경했는데 Dependabot이 해당 Lockfile 형식을 지원하지 않는다면 자동 의존성 업데이트가 중단될 수 있다.
이번 변경은 이러한 호환성 범위를 확대한 것이다.
다만 패키지 매니저 지원이 추가됐다는 사실이 해당 버전의 모든 기능과 프로젝트 설정이 완벽하게 지원된다는 보장은 아니다.
실제 자동 업데이트가 정상적으로 생성되는지 확인할 필요가 있다.
이번 기능은 github.com에서 제공된다.
GitHub Enterprise Server에는 3.24 버전부터 포함될 예정이다.
GitHub, Branch Protection Rules의 Ruleset 일괄 이전 지원 — 기존 정책 유지와 단계적 적용 가능
- 발표일: 2026-10-20 — 정확한 게시 시각 미확인
- 적용 대상: GitHub Repository Rulesets
- 원문: https://github.blog/changelog/2026-10-20-bulk-migrate-branch-protections-to-rulesets-for-a-repository/
GitHub가 하나의 Repository에 존재하는 여러 Branch Protection Rule을 한 번에 Ruleset으로 변환하는 기능을 추가했다.
Branch Protection은 GitHub Repository의 특정 브랜치에 적용하는 보호 정책이다.
예를 들어 main 브랜치에 다음과 같은 조건을 적용할 수 있다.
- PR을 통해서만 변경 허용
- 지정된 수의 리뷰 승인 요구
- CI 테스트 통과 요구
- 직접 Push 제한
- Force Push 제한
GitHub는 이러한 정책을 더 유연하게 관리하기 위한 Ruleset 기능도 제공한다.
Ruleset은 여러 브랜치에 공통 정책을 적용하거나 Organization 단위로 규칙을 관리하는 데 사용할 수 있다.
기존에도 개별 Branch Protection Rule을 Ruleset으로 변환하는 기능은 존재했다.
이번 업데이트는 Repository 안의 모든 Branch Protection Rule을 하나의 작업으로 이전할 수 있도록 확장했다.
일괄 이전 과정
Repository의 Settings에서 Rulesets 화면으로 이동하면 기존 Branch Protection Rule을 가져오는 기능을 사용할 수 있다.
해당 기능의 이름은 다음과 같다.
Import branch protection rules
실행하면 GitHub는 기존 보호 규칙을 분석해 새로운 Ruleset을 생성할 수 있는지 확인한다.
관리자는 실제 변경 전에 생성될 Ruleset을 미리 볼 수 있다.
이 과정에서는 다음 정보를 확인할 수 있다.
- 어떤 Ruleset이 생성되는지
- 기존 Ruleset과 중복되는 규칙이 있는지
- 하나의 기존 규칙이 여러 Ruleset으로 변환되는지
- 각 Ruleset을 어떤 상태로 생성할지
일부 Branch Protection Rule은 하나의 Ruleset으로 변환되지 않을 수 있다.
GitHub는 기존 정책의 동작을 유지하기 위해 하나의 규칙을 여러 Ruleset으로 분리할 수 있다고 설명한다.
따라서 기존 규칙 두 개를 변환한다고 해서 반드시 Ruleset 두 개가 생성되는 것은 아니다.
적용 상태 선택
관리자는 새 Ruleset의 적용 상태를 선택할 수 있다.
| 상태 | 의미 |
|---|---|
| Active | 규칙을 실제로 적용 |
| Evaluate | 규칙의 영향을 평가 |
| Disabled | 규칙 비활성화 |
이러한 상태는 기존 정책을 새로운 구조로 이전할 때 중요하다.
예를 들어 기존 Branch Protection을 즉시 삭제하고 새로운 Ruleset을 적용하면 예상하지 못한 PR 병합 실패가 발생할 수 있다.
반면 Evaluate 상태로 규칙의 영향을 먼저 확인하면 실제 차단 정책을 적용하기 전에 설정을 검토할 수 있다.
관리자는 Ruleset 생성 이후 기존 Branch Protection Rule을 유지할지 삭제할지도 선택할 수 있다.
따라서 기존 정책을 보존하면서 단계적으로 새로운 규칙 구조로 이전할 수 있다.
Ruleset의 관리 범위
GitHub는 Ruleset의 장점으로 다음 기능을 제시한다.
- 패턴을 이용한 여러 브랜치 대상 지정
- 여러 Ruleset의 중첩 적용
- Organization 또는 Enterprise 단위 관리
- 사용자·팀·GitHub App별 예외 권한 설정
예를 들어 여러 Repository의 release/* 브랜치에 동일한 보호 정책을 적용할 수 있다.
또는 운영 브랜치에 기본 정책을 적용하면서 특정 팀에만 별도의 추가 정책을 적용할 수 있다.
다만 Ruleset이 여러 개 중첩될 때는 실제로 어떤 규칙이 적용되는지 확인해야 한다.
기존 정책보다 의도하지 않게 엄격한 조건이 적용될 수 있기 때문이다.
이번 기능은 Ruleset을 사용할 수 있는 Repository에서 제공된다.
새로운 보안 분석 기능을 추가한 것은 아니지만, 기존 브랜치 보호 정책을 더 유연한 관리 구조로 이전하는 작업을 자동화한 변경이다.
📚 추천 글
Improving navigation experience in v0 with Next.js 16.4
- 저자: Aurora Scharff / Next.js
- 게시일: 2026-10-16
- 원문: https://nextjs.org/blog/improving-navigation-experience-in-v0-with-nextjs-16-4
Vercel이 자사의 AI 애플리케이션 빌더인 v0에서 Next.js 16.4의 Partial Prefetching과 Cache Components를 이용해 페이지 이동 경험을 개선한 과정을 설명한 기술 글이다.
이 글은 단순히 Next.js의 새로운 API를 소개하는 데 그치지 않는다.
실제 서비스에서 페이지 이동이 느리게 느껴지는 원인을 분석하고, 캐싱과 Prefetching을 변경한 뒤 운영 비용까지 측정한 사례다.
특히 사용자 경험을 개선하기 위해 네트워크 요청을 늘리면 오히려 운영 비용이 증가할 수 있다는 점을 구체적으로 보여준다.
기존 페이지 전환의 문제
v0는 하나의 웹 애플리케이션 안에서 여러 화면을 제공한다.
대표적으로 다음과 같은 화면이 있다.
- 채팅
- 프로젝트 목록
- 프로젝트 상세
- 템플릿 목록
- 템플릿 상세
- 계정 설정
기존 구현에서는 사용자가 이미 방문했던 화면으로 돌아가더라도 Loading Skeleton이 다시 표시되는 경우가 있었다.
예를 들어 템플릿 상세 페이지를 열었다가 목록으로 돌아온 뒤 같은 템플릿을 다시 클릭해도 콘텐츠를 다시 가져오는 과정이 발생할 수 있었다.
이러한 동작은 브라우저가 전체 HTML 문서를 다시 요청하는 전통적인 페이지 이동과 다를 수 있다.
하지만 사용자에게는 여전히 화면이 다시 로딩되는 것처럼 느껴진다.
저자는 이를 해결하기 위해 두 가지 목표를 정했다.
첫 번째는 사용자가 링크를 클릭하기 전에 필요한 콘텐츠를 미리 준비하는 것이다.
두 번째는 이미 방문했던 화면의 콘텐츠를 다시 사용할 수 있도록 유지하는 것이다.
Next.js Partial Prefetching
Partial Prefetching은 페이지 전체를 미리 가져오는 대신 재사용할 수 있는 부분을 우선 가져오는 방식이다.
기존 Next.js에서 prefetch={true}를 사용하면 동적 콘텐츠를 포함한 목적지 전체를 가져올 수 있었다.
문제는 사용자가 실제로 방문하지 않을 페이지까지 미리 가져올 수 있다는 점이다.
예를 들어 템플릿 카드가 100개 존재하고 화면에 표시된 카드마다 상세 정보를 미리 가져온다면 사용자가 한 개의 카드만 클릭하더라도 많은 요청이 발생할 수 있다.
반대로 Prefetching을 완전히 비활성화하면 사용자가 클릭한 이후에야 필요한 데이터 요청이 시작된다.
Partial Prefetching은 이러한 두 방식 사이에서 재사용 가능한 콘텐츠를 선택적으로 준비하도록 한다.
저자는 이 기능을 실제 v0의 여러 화면에 적용했다.
instant()를 이용한 페이지 전환 테스트
이번 글에서 가장 중요한 접근 가운데 하나는 페이지 이동의 체감 속도를 자동화된 테스트로 정의한 것이다.
일반적인 E2E 테스트에서는 링크를 클릭한 뒤 최종 화면이 정상적으로 표시되는지 확인한다.
하지만 최종 화면이 나타났다는 사실만으로 이동 과정이 빠르게 느껴졌는지는 알기 어렵다.
예를 들어 3초 동안 Loading Skeleton을 표시한 뒤 최종 화면이 나타나더라도 테스트는 통과할 수 있다.
저자는 이러한 문제를 해결하기 위해 @next/playwright의 instant() Helper를 사용했다.
이 Helper는 클릭 이후에 도착하는 콘텐츠를 제한한 상태에서 클릭 직전에 준비된 UI를 검사할 수 있도록 한다.
따라서 다음 조건을 테스트할 수 있다.
- 템플릿 상세 화면의 제목이 이미 준비돼 있는지
- 주요 미리보기 콘텐츠가 표시되는지
- 넓은 영역을 차지하는 Loading Skeleton이 나타나지 않는지
핵심은 단순히 최종 화면이 표시되는지를 검사하는 것이 아니다.
클릭 이전에 충분한 정보가 준비돼 있어 사용자에게 즉각적인 전환으로 느껴지는지를 검증하는 것이다.
이러한 테스트는 CI에서 반복 실행할 수 있다.
따라서 이후 코드 변경으로 Prefetching이 다시 비효율적으로 동작하면 회귀 문제를 발견할 수 있다.
공개 데이터와 사용자별 데이터 분리
저자는 템플릿 상세 페이지에서 필요한 데이터를 두 종류로 나눴다.
첫 번째는 모든 사용자가 공유할 수 있는 공개 템플릿 데이터다.
예를 들어 템플릿의 제목과 설명, 미리보기 정보가 해당할 수 있다.
두 번째는 특정 사용자에게만 해당하는 개인화 데이터다.
예를 들어 사용자가 해당 템플릿을 저장했는지 여부가 포함될 수 있다.
이 두 종류의 데이터를 동일한 방식으로 캐싱하면 문제가 발생할 수 있다.
공개 데이터는 여러 사용자 사이에서 재사용할 수 있다.
하지만 사용자별 데이터는 다른 사용자에게 공유돼서는 안 된다.
저자는 공개 데이터에 다음 캐시 지시문을 사용했다.
'use cache: remote'
Vercel 환경에서는 이를 Runtime Cache에 저장한다.
여러 사용자와 Function 인스턴스가 같은 공개 데이터를 재사용할 수 있다.
하지만 자체 호스팅 환경에서는 기본 캐시 구성이 다를 수 있다.
저자는 next start 환경에서 별도의 Remote Cache Handler를 설정하지 않으면 메모리 기반으로 처리될 수 있다고 설명한다.
따라서 동일한 Next.js 코드를 사용하더라도 실제 배포 환경에 따라 캐시의 저장 위치가 다를 수 있다.
Cache Lifetime과 무효화
캐시를 적용할 때는 데이터가 언제까지 유효한지 결정해야 한다.
저자는 다음 기능을 사용했다.
cacheLife()cacheTag()revalidateTag()
cacheLife()는 데이터의 캐시 수명을 정의한다.
cacheTag()는 캐시된 데이터에 식별자를 부여한다.
revalidateTag()는 해당 식별자와 연결된 캐시를 다시 검증하도록 한다.
예를 들어 템플릿 ID가 123이라고 가정해 보자.
해당 템플릿의 공개 데이터를 캐싱할 때 template:123이라는 태그를 부여할 수 있다.
이후 템플릿이 수정되면 해당 태그를 무효화할 수 있다.
이렇게 하면 모든 템플릿의 캐시를 삭제하지 않고 변경된 데이터와 관련된 캐시만 갱신할 수 있다.
하지만 캐시를 무효화하는 코드가 실제 데이터 수정 과정과 연결돼 있어야 한다.
데이터가 변경됐는데 관련 Cache Tag가 갱신되지 않으면 사용자에게 오래된 데이터가 표시될 수 있기 때문이다.
Prefetching으로 요청량이 2.9배 증가한 문제
저자는 템플릿 상세 페이지에 prefetch={true}를 적용했다.
그 결과 사용자가 템플릿을 클릭할 때 주요 콘텐츠가 이미 준비된 상태로 표시됐다.
하지만 새로운 문제가 발생했다.
템플릿 목록에 표시된 카드들이 더 많은 정보를 미리 요청하기 시작한 것이다.
실제 운영 데이터를 확인한 결과 템플릿 상세 페이지의 Prefetch 요청량이 2.9배 증가했다.
이는 페이지 이동 경험을 개선하는 과정에서 네트워크 요청과 캐시 사용 비용이 증가할 수 있다는 사실을 보여준다.
저자는 이를 해결하기 위해 모든 카드의 상세 정보를 즉시 가져오는 방식을 변경했다.
사용자가 실제로 해당 카드를 열 가능성이 높아진 시점에만 자세한 Prefetching을 시작하도록 했다.
사용자 의도 기반 Prefetching
저자는 다음 사용자 행동을 Prefetching의 신호로 사용했다.
- 마우스를 카드 위로 이동
- 키보드로 카드에 포커스
- 터치 시작
이러한 행동이 발생하면 해당 카드의 상세 콘텐츠를 미리 가져온다.
반면 화면에 표시됐지만 사용자가 상호작용하지 않은 카드는 기본적인 Prefetch 정책을 유지한다.
핵심 설정은 다음과 같다.
prefetch={intent ? true : 'auto'}
사용자가 특정 카드에 관심을 보이는 순간 intent가 활성화된다.
이후 상세 콘텐츠의 Prefetching을 수행한다.
이 방식은 사용자가 클릭할 가능성이 있는 항목에 리소스를 집중한다.
반면 모든 항목에 같은 수준의 Prefetching을 적용하지 않으므로 불필요한 요청을 줄일 수 있다.
다만 마우스 Hover가 발생했다고 해서 사용자가 반드시 클릭하는 것은 아니다.
사용자가 빠르게 화면을 지나가는 경우에도 Prefetching이 시작될 수 있다.
따라서 사용자 의도 기반 Prefetching 역시 모든 불필요한 요청을 제거하는 것은 아니다.
사용자 경험과 네트워크 비용 사이에서 더 나은 균형을 찾기 위한 방식이다.
개인화 데이터는 브라우저에 캐싱
계정 설정 페이지에서는 다른 접근이 필요했다.
설정 데이터는 사용자와 조직에 따라 달라질 수 있기 때문이다.
예를 들어 A 조직의 설정이 B 조직의 사용자에게 표시돼서는 안 된다.
저자는 이러한 데이터에 다음 지시문을 사용했다.
'use cache: private'
이 방식은 개인화된 콘텐츠를 브라우저에서 재사용하도록 한다.
예를 들어 특정 사용자가 고급 설정 페이지를 열었다가 다른 설정 화면으로 이동한 뒤 다시 돌아올 수 있다.
이때 이미 가져온 설정 데이터를 재사용하면 Loading Skeleton을 다시 표시할 필요가 줄어든다.
저자는 해당 캐시의 Stale 시간을 300초로 설정했다.
또한 조직이나 계정을 나타내는 scope 값을 캐시 함수의 인자로 전달했다.
이렇게 하면 해당 값이 캐시 Key의 일부가 된다.
따라서 서로 다른 조직의 설정 데이터가 같은 캐시 항목으로 처리되지 않도록 할 수 있다.
그러나 캐시 Key가 올바르게 구성됐다는 사실만으로 인증과 권한 검사가 불필요해지는 것은 아니다.
실제 서버에서는 사용자가 해당 조직의 설정을 조회할 수 있는지 별도로 확인해야 한다.
설정 변경 이후 캐시 갱신
개인화 데이터를 캐싱하면 데이터 수정 이후의 동작도 중요하다.
예를 들어 사용자가 설정을 변경했는데 이전 캐시가 그대로 표시되면 사용자는 저장이 실패했다고 생각할 수 있다.
저자는 Server Action에서 다음 함수를 사용했다.
updateTag()
이 함수는 관련 캐시를 즉시 만료시킨다.
따라서 설정을 저장한 뒤 새로운 값을 확인할 수 있다.
이러한 구조에서는 데이터를 읽는 함수와 데이터를 변경하는 함수 사이의 관계를 명확하게 정의해야 한다.
특히 여러 화면에서 같은 데이터를 사용하는 경우 어떤 변경이 어떤 캐시를 무효화해야 하는지 관리할 필요가 있다.
navigation()을 이용한 작업 우선순위 조정
Next.js 16.4에는 새로운 navigation() API도 포함돼 있다.
이 API는 캐시된 작업 가운데 일부를 페이지 이동이 시작된 이후로 미룰 수 있도록 한다.
예를 들어 프로젝트 상세 화면을 열 때 다음 정보가 필요할 수 있다.
- 프로젝트 제목
- 현재 배포 상태
- 최근 채팅 목록
하지만 세 항목의 중요도는 같지 않다.
프로젝트 제목과 배포 상태는 사용자가 화면에 들어오자마자 확인해야 할 수 있다.
반면 최근 채팅 목록은 조금 늦게 표시돼도 전체 페이지 이동을 이해하는 데 큰 문제가 없을 수 있다.
저자는 이러한 차이를 이용했다.
프로젝트 제목과 배포 상태는 클릭 이전에 Prefetch한다.
최근 채팅 목록은 navigation() 이후 가져오도록 한다.
이렇게 하면 중요한 정보는 빠르게 표시하면서 상대적으로 덜 중요한 데이터의 요청은 필요한 시점까지 미룰 수 있다.
다만 모든 데이터를 navigation() 이후로 이동하면 사용자가 클릭한 다음에 수행해야 하는 작업이 증가한다.
따라서 실제 UI에서 어떤 콘텐츠가 먼저 필요한지 결정해야 한다.
실제 운영 비용의 변화
저자는 여러 화면에 변경을 적용한 뒤 운영 데이터를 분석했다.
결과는 다음과 같았다.
| 측정 항목 | 변경 이후 결과 |
|---|---|
| Shared Runtime Cache 비용 | 기존의 4.3배 |
| Runtime Cache 추가 비용 | 하루 약 3.44달러 |
| 요청 처리 관련 비용 | 14.4% 감소 |
| 전체 일일 비용 | 2.9% 감소 |
흥미로운 점은 캐시 사용 비용이 증가했지만 전체 비용은 감소했다는 것이다.
Prefetching과 Caching을 확대하면 캐시 읽기와 쓰기에 필요한 비용이 증가할 수 있다.
하지만 반대로 서버가 같은 데이터를 반복해서 계산하는 비용은 줄어들 수 있다.
또한 사용자 요청을 처리하는 데 필요한 CPU와 데이터 전송량에도 변화가 발생할 수 있다.
저자는 이러한 비용을 함께 비교했다.
다만 이는 실제 운영 환경의 변경 전후 데이터를 비교한 결과다.
트래픽과 사용자 행동, 다른 배포 변경의 영향을 완전히 제거한 독립적인 통제 실험은 아니다.
따라서 모든 Next.js 애플리케이션에서 같은 비용 절감 효과가 발생한다고 단정할 수 없다.
설계상의 핵심적인 트레이드오프
이 글은 Prefetching을 많이 적용할수록 무조건 사용자 경험이 개선된다는 접근을 취하지 않는다.
오히려 다음 세 가지 문제를 동시에 고려한다.
첫 번째는 클릭 이후 주요 콘텐츠가 얼마나 빠르게 표시되는가다.
두 번째는 사용자가 열지 않을 페이지를 위해 얼마나 많은 요청을 미리 보내는가다.
세 번째는 캐싱과 Prefetching에 필요한 비용이 실제 요청 처리 비용을 얼마나 줄이는가다.
저자는 이를 해결하기 위해 공개 데이터와 개인화 데이터를 분리하고, 사용자의 의도에 따라 Prefetching을 실행하며, 콘텐츠의 중요도에 따라 작업을 지연시켰다.
특히 성능 최적화의 결과를 단순한 페이지 로딩 시간뿐 아니라 실제 테스트와 운영 비용으로 검증했다는 점이 특징이다.
What is OctaneJS? A compiler-first alternative to React
- 저자: Emmanuel John / LogRocket
- 게시일: 2026-10-20
- 원문: https://blog.logrocket.com/octanejs-compiler-first-react-alternative/
React와 유사한 컴포넌트 및 Hook 모델을 유지하면서 Virtual DOM 없이 UI를 업데이트하도록 설계된 OctaneJS의 구조를 분석한 글이다.
저자는 OctaneJS의 특징을 설명한 뒤 실제 Task Dashboard를 구현하면서 상태 관리와 비동기 데이터 처리, 리스트 렌더링, 이벤트 처리 방식을 비교한다.
OctaneJS는 Ahead-of-Time(AOT) 컴파일 방식을 사용하는 프론트엔드 프레임워크다.
AOT 컴파일은 프로그램을 실행하기 전에 소스코드를 분석하고 실행에 필요한 구조를 미리 생성하는 방식이다.
일반적인 React에서는 상태가 변경되면 React가 새로운 UI 트리를 계산하고 기존 트리와 비교해 필요한 DOM 변경을 수행한다.
OctaneJS는 이러한 작업 가운데 일부를 컴파일 시점으로 옮기려 한다.
Virtual DOM을 사용하지 않는 구조
React는 선언적인 컴포넌트 모델을 제공한다.
개발자는 현재 상태에 따라 어떤 UI가 표시돼야 하는지 정의한다.
이후 React가 실제 DOM을 업데이트한다.
React의 Reconciliation은 이전 UI 트리와 새로운 UI 트리 사이의 차이를 계산하는 과정이다.
OctaneJS는 이러한 차이 계산을 런타임에서 반복하는 대신 컴파일러가 미리 어떤 상태가 어떤 DOM 요소에 영향을 주는지 분석한다.
예를 들어 하나의 상태가 특정 텍스트 요소에만 영향을 준다고 가정해 보자.
OctaneJS는 해당 상태가 변경됐을 때 실제로 수정해야 하는 DOM 요소를 컴파일 단계에서 결정할 수 있다.
따라서 런타임에서는 관련된 DOM을 직접 업데이트한다.
이러한 구조는 Virtual DOM을 생성하고 비교하는 비용을 줄이려는 목적을 가진다.
하지만 Virtual DOM을 사용하지 않는다는 사실만으로 모든 애플리케이션에서 더 높은 성능을 보장하는 것은 아니다.
실제 성능은 상태 변경의 범위와 DOM 구조, 컴파일러가 생성하는 코드의 효율성 등에 영향을 받는다.
또한 이번 글은 OctaneJS와 React의 렌더링 성능을 독립적으로 측정한 Benchmark를 제공하지 않는다.
React Hook과 유사한 API
OctaneJS는 React 개발자에게 익숙한 여러 API를 제공한다.
대표적으로 다음 기능이 있다.
useStateuseEffectuseRefuseContext- Suspense와 유사한 비동기 UI 처리
하지만 내부 동작은 React와 다르다.
특히 Hook의 호출 순서를 처리하는 방식이 다르다.
React에서는 Hook을 컴포넌트의 최상위 수준에서 호출해야 한다.
조건문이나 반복문 안에서 임의로 Hook을 호출하면 호출 순서가 달라질 수 있기 때문이다.
React는 Hook 호출 순서를 이용해 각 Hook의 상태를 연결한다.
따라서 조건에 따라 Hook 호출이 생략되면 서로 다른 상태가 잘못 연결될 수 있다.
OctaneJS는 이 문제를 다른 방식으로 처리한다.
컴파일 시점의 Hook Slot
OctaneJS는 각 Hook 호출 위치에 정적인 Slot을 할당한다.
Slot은 해당 Hook을 식별하기 위한 위치다.
컴파일러가 코드의 구조를 분석하고 각 Hook이 어떤 Slot을 사용하는지 미리 결정한다.
따라서 런타임에서 Hook을 호출하는 순서에만 의존하지 않는다.
이러한 구조에서는 조건문 안에 Hook이 존재하더라도 컴파일러가 해당 Hook의 위치를 식별할 수 있다.
이는 React의 Rules of Hooks와 다른 프로그래밍 모델이다.
하지만 OctaneJS의 Hook이 React Hook과 이름이 같다고 해서 내부 동작까지 완전히 동일한 것은 아니다.
따라서 React 라이브러리를 OctaneJS에 그대로 가져와 사용할 수 있다고 가정해서는 안 된다.
Hook 의존성 자동 추론
React에서는 useEffect나 useMemo에 의존성 배열을 전달하는 경우가 많다.
예를 들어 특정 상태가 변경됐을 때만 Effect를 다시 실행하도록 설정할 수 있다.
하지만 의존성 배열을 잘못 작성하면 문제가 발생할 수 있다.
필요한 변수를 누락하면 오래된 값을 참조할 수 있다.
반대로 불필요한 변수를 포함하면 Effect가 자주 실행될 수 있다.
OctaneJS는 이러한 의존성의 일부를 컴파일러가 추론하도록 한다.
컴파일러는 함수가 참조하는 변수를 분석하고 어떤 상태가 변경됐을 때 다시 계산해야 하는지 결정한다.
따라서 일반적인 사용 사례에서는 개발자가 의존성 배열을 수동으로 작성하는 작업을 줄일 수 있다.
다만 컴파일러의 의존성 추론이 실제 프로그램의 모든 외부 상태 변경을 이해한다는 의미는 아니다.
예를 들어 React 외부의 변경 가능한 객체나 전역 상태를 사용하는 경우에는 해당 프레임워크의 반응성 규칙을 이해해야 한다.
TSRX: 컴파일러가 이해하는 제어 흐름
OctaneJS는 TSRX라는 문법을 사용한다.
TSRX는 TSX와 유사하지만 UI 구조를 컴파일러가 더 명확하게 분석할 수 있도록 새로운 제어 흐름 문법을 추가한다.
대표적인 기능은 다음과 같다.
| 문법 | 역할 |
|---|---|
@if | 조건에 따른 UI 분기 |
@for | 리스트 렌더링 |
@switch | 여러 조건 분기 |
@try | 비동기 작업과 오류 처리 |
@pending | 데이터 로딩 상태 |
@catch | 오류 발생 상태 |
예를 들어 React에서는 배열을 렌더링하기 위해 map()을 사용하는 경우가 많다.
OctaneJS에서는 @for를 사용할 수 있다.
컴파일러는 해당 구조가 리스트 렌더링이라는 사실을 직접 이해한다.
따라서 Key를 이용해 필요한 요소를 추가하고 제거하는 코드를 생성할 수 있다.
@if 역시 일반적인 JavaScript 조건문보다 UI의 분기 구조를 명시적으로 나타낸다.
컴파일러는 이러한 정보를 이용해 필요한 DOM 업데이트를 준비한다.
하지만 TSRX는 일반적인 TypeScript와 TSX의 표준 문법이 아니다.
따라서 기존 TypeScript 도구와의 호환성은 OctaneJS의 컴파일러 및 관련 개발 도구 지원에 영향을 받는다.
비동기 UI 처리
저자는 실제 Task Dashboard를 구현하면서 비동기 데이터 처리 방식을 비교한다.
예제에서는 팀별 작업 목록을 외부 API에서 가져온다.
사용자가 다른 팀을 선택하면 새로운 작업 목록을 요청한다.
이때 다음 세 가지 상태가 발생할 수 있다.
- 데이터 요청 중
- 데이터 요청 성공
- 데이터 요청 실패
OctaneJS에서는 이러한 상태를 @try, @pending, @catch로 표현할 수 있다.
컴파일러는 세 상태를 하나의 UI 영역에 대한 서로 다른 표현으로 이해한다.
React에서도 Suspense와 Error Boundary를 이용해 비슷한 기능을 구현할 수 있다.
하지만 OctaneJS는 이를 컴파일러가 직접 분석할 수 있는 제어 흐름 문법으로 제공한다.
이 차이는 단순한 API 이름의 차이가 아니다.
비동기 작업과 UI 상태의 관계를 컴파일 단계에서 얼마나 명확하게 표현할 것인가라는 설계 선택의 차이다.
리스트와 상태 관리
저자는 작업 목록에서 다음 기능을 구현했다.
- 새로운 작업 추가
- 완료 여부 변경
- 작업 이름 수정
- 작업 삭제
- 검색어에 따른 필터링
- 완료 상태에 따른 목록 분리
데이터는 비동기 API에서 가져온다.
이후 사용자 입력에 따라 로컬 상태가 변경된다.
예를 들어 사용자가 작업의 완료 상태를 변경하면 UI가 즉시 업데이트될 수 있다.
동시에 서버 API에도 변경 요청을 보낸다.
이러한 구조는 Optimistic Update와 유사하다.
Optimistic Update는 서버 응답을 기다리지 않고 먼저 UI를 변경하는 방식이다.
하지만 서버 요청이 실패할 가능성을 고려해야 한다.
예제에서는 UI 업데이트 구조를 설명하는 데 초점을 맞추고 있으므로 실제 운영 서비스에서는 실패 시 원래 상태로 되돌리는 Rollback 처리나 재시도 정책이 추가로 필요할 수 있다.
Native DOM Event와 React Synthetic Event
OctaneJS는 브라우저의 기본 DOM Event를 직접 사용한다.
React는 Synthetic Event 시스템을 통해 이벤트를 처리한다.
Synthetic Event는 브라우저 이벤트를 React의 이벤트 시스템과 연결하기 위한 추상화다.
OctaneJS에서는 이벤트 Handler가 기본 DOM Event를 직접 받는다.
따라서 onInput, onChange, onBlur 등의 동작을 브라우저의 기본 이벤트 의미에 맞춰 처리한다.
이러한 차이는 React에서 사용하던 이벤트 처리 코드를 이전할 때 영향을 줄 수 있다.
특히 이벤트의 발생 시점과 전달되는 객체의 타입을 확인해야 한다.
컴포넌트 범위의 CSS
OctaneJS는 컴포넌트 안에 작성한 CSS를 컴파일 단계에서 처리한다.
컴포넌트의 스타일은 자동으로 범위가 제한된다.
이는 전역 CSS 클래스 이름이 충돌하는 문제를 줄이기 위한 기능이다.
예를 들어 서로 다른 두 컴포넌트에서 .button이라는 클래스를 사용하더라도 해당 스타일이 다른 컴포넌트에 영향을 주지 않도록 할 수 있다.
다만 CSS가 컴포넌트 범위로 제한된다고 해서 모든 디자인 시스템 문제가 해결되는 것은 아니다.
공통 디자인 토큰과 테마, 접근성 스타일 등을 어떻게 공유할지는 별도로 설계해야 한다.
React와의 호환성 및 안정화 단계
OctaneJS는 현재 Beta 단계다.
저자는 실제 프로젝트에서 사용한다면 버전을 고정하도록 안내한다.
또한 React와 비슷한 API를 제공하지만 React의 완전한 Drop-in Replacement라고 가정해서는 안 된다.
기존 React 생태계에는 많은 라이브러리가 존재한다.
이러한 라이브러리는 React의 런타임과 컴포넌트 동작, Hook 규칙에 의존할 수 있다.
OctaneJS가 비슷한 Hook을 제공한다고 해서 해당 라이브러리들을 수정 없이 사용할 수 있는 것은 아니다.
또한 JSX와 다른 TSRX 문법을 사용하므로 기존 도구와의 통합도 고려해야 한다.
이번 글은 특정 프레임워크가 React보다 항상 우수하다고 주장하는 대신 React와 유사한 개발 경험을 유지하면서 런타임의 책임을 컴파일 단계로 이동하는 아키텍처의 장단점을 설명한다.
특히 Hook의 실행 순서와 의존성 추론, Virtual DOM의 역할을 비교해 이해할 수 있다는 점에서 참고할 만하다.
WCAG 2.2 React checklist: each component, and the code I use for AA
- 저자: Maniruzzaman Jubayer
- 게시일: 2026-10-16
- 원문: https://maniruzzamanjubayer.com/blog/wcag-2-2-react-checklist
React와 Next.js 애플리케이션에서 WCAG 2.2 AA 수준의 웹 접근성을 충족하기 위해 컴포넌트와 테스트를 어떻게 설계해야 하는지 설명한 글이다.
WCAG(Web Content Accessibility Guidelines)는 장애가 있는 사용자도 웹 콘텐츠에 접근할 수 있도록 하기 위한 지침이다.
웹 접근성은 단순히 이미지에 대체 텍스트를 추가하는 문제만은 아니다.
키보드로 화면을 조작할 수 있는지, 스크린 리더가 상태 변화를 알 수 있는지, 인증 과정에 불필요한 인지적 부담이 존재하지 않는지 등 다양한 요소가 포함된다.
저자는 실제 React 애플리케이션에서 자주 발생하는 접근성 문제를 컴포넌트 단위로 정리했다.
특히 특정 페이지 하나를 수정하는 것보다 공통 컴포넌트의 구조를 올바르게 설계하는 편이 장기적으로 효과적이라고 설명한다.
WCAG 2.2에서 변경된 주요 항목
WCAG 2.2에는 새로운 성공 기준이 추가됐다.
이 가운데 A와 AA 수준에 포함되는 주요 기준은 다음과 같다.
| 기준 | 수준 | 주요 내용 |
|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | AA | 키보드 포커스 요소가 다른 UI에 완전히 가려지지 않아야 함 |
| 2.5.7 Dragging Movements | AA | 드래그 없이도 동일한 기능을 수행할 수 있어야 함 |
| 2.5.8 Target Size (Minimum) | AA | 클릭 및 터치 대상의 최소 크기 또는 간격 고려 |
| 3.2.6 Consistent Help | A | 도움말의 위치와 순서가 일관돼야 함 |
| 3.3.7 Redundant Entry | A | 이미 입력한 정보를 불필요하게 다시 요구하지 않아야 함 |
| 3.3.8 Accessible Authentication (Minimum) | AA | 인증 과정에서 불필요한 인지 기능 테스트를 요구하지 않아야 함 |
또한 기존 4.1.1 Parsing 기준은 제거됐다.
하지만 해당 기준이 제거됐다고 해서 잘못된 HTML 구조가 문제가 되지 않는 것은 아니다.
예를 들어 중복된 ID로 인해 Label이 잘못된 입력 요소와 연결된다면 다른 접근성 기준을 위반할 수 있다.
따라서 HTML의 구조적인 정확성은 계속 중요하다.
Sticky Header가 키보드 포커스를 가리는 문제
React 애플리케이션에서는 화면 상단에 고정된 Header를 사용하는 경우가 많다.
또한 화면 하단에 고정된 Bottom Navigation이나 Cookie Banner가 존재할 수 있다.
문제는 사용자가 Tab 키로 화면을 탐색할 때 발생한다.
브라우저가 포커스된 요소를 화면에 표시하려고 스크롤했지만 해당 요소가 고정된 Header 뒤에 완전히 가려질 수 있다.
이 경우 사용자는 현재 어떤 요소에 포커스가 있는지 알기 어렵다.
WCAG 2.2의 Focus Not Obscured 기준은 이러한 문제를 다룬다.
저자는 CSS의 scroll-padding을 이용하는 방법을 소개한다.
예를 들어 상단 Header와 하단 고정 UI의 높이를 고려해 스크롤 여백을 설정할 수 있다.
이렇게 하면 브라우저가 포커스 요소를 화면에 표시할 때 고정 UI와 겹치는 문제를 줄일 수 있다.
하지만 scroll-padding을 추가했다고 해서 모든 레이아웃에서 문제가 해결된다고 보장할 수는 없다.
실제 Header의 높이와 화면 크기, 고정 요소의 위치에 따라 추가적인 처리가 필요할 수 있다.
Dragging Movements의 대체 수단
드래그 기능은 웹 애플리케이션에서 자주 사용된다.
예를 들어 Kanban Board에서 작업 카드를 이동하거나, 정렬 가능한 리스트에서 항목의 순서를 변경할 수 있다.
하지만 모든 사용자가 드래그 동작을 수행할 수 있는 것은 아니다.
운동 기능에 제한이 있는 사용자나 정밀한 포인터 조작이 어려운 사용자에게 드래그는 큰 장벽이 될 수 있다.
저자는 이러한 기능에 클릭하거나 터치할 수 있는 대체 조작 방법을 제공하도록 안내한다.
예를 들어 정렬 가능한 리스트의 각 항목에 다음 버튼을 추가할 수 있다.
- 위로 이동
- 아래로 이동
이렇게 하면 사용자는 드래그하지 않고도 항목의 순서를 변경할 수 있다.
중요한 점은 키보드 단축키만 제공하는 방식이 항상 충분하지는 않다는 것이다.
WCAG 2.2의 Dragging Movements 기준은 드래그를 대체할 수 있는 단일 포인터 조작 방법을 요구하기 때문이다.
따라서 마우스나 터치로 클릭할 수 있는 버튼을 제공하는 방식이 적합할 수 있다.
클릭 대상의 최소 크기
WCAG 2.2 AA에는 Target Size (Minimum) 기준도 추가됐다.
일반적으로 클릭 대상의 크기는 최소 24×24 CSS Pixel을 고려한다.
예를 들어 테이블의 각 행에 16px 크기의 삭제 아이콘이 존재할 수 있다.
아이콘 자체가 작더라도 버튼의 실제 클릭 영역은 더 크게 만들 수 있다.
이렇게 하면 정밀한 포인터 조작이 어려운 사용자가 해당 버튼을 누르기 쉬워진다.
다만 WCAG에는 간격이나 특정 상황에 관한 예외가 존재한다.
따라서 모든 텍스트 링크나 작은 요소를 무조건 24×24px로 만들어야 한다는 의미는 아니다.
실제 성공 기준과 예외 조건을 함께 확인해야 한다.
div를 버튼처럼 사용하는 문제
저자가 자주 발견한 React 접근성 문제 가운데 하나는 일반적인 div 요소에 onClick을 추가해 버튼처럼 사용하는 구현이다.
예를 들어 다음과 같은 구조다.
<div onClick={archive}>Archive</div>
마우스로 클릭하면 정상적으로 동작할 수 있다.
하지만 기본적으로 키보드 포커스를 받을 수 있는 요소가 아니다.
따라서 사용자가 Tab 키로 탐색할 때 해당 요소를 건너뛸 수 있다.
스크린 리더도 해당 요소를 버튼으로 인식하지 못할 수 있다.
저자는 이러한 경우 기본 HTML의 <button> 요소를 사용하도록 안내한다.
기본 Button은 다음 기능을 제공한다.
- 키보드 포커스
- Enter 및 Space 키 처리
- 스크린 리더에 전달되는 Button 역할
- 비활성화 상태 표현
이러한 기능을 직접 구현하는 것보다 기본 HTML 요소를 사용하는 편이 안전한 경우가 많다.
또한 다른 페이지로 이동하는 요소는 Button이 아니라 Link를 사용하는 것이 적절하다.
즉 기능의 의미에 따라 HTML 요소를 선택해야 한다.
ESLint의 기본 접근성 규칙만으로는 충분하지 않다
저자는 Next.js 프로젝트의 Lint 설정도 검토했다.
실제 Next.js 16.4.0 환경에서 클릭 가능한 div와 대체 텍스트가 없는 이미지를 포함한 코드를 검사했다.
기본 설정에서는 이미지 관련 경고가 표시됐지만 클릭 가능한 div의 문제가 모두 오류로 처리되지는 않았다.
또한 경고만 발생한 상태에서 Lint 실행이 성공으로 종료될 수 있었다.
저자는 이를 보완하기 위해 eslint-plugin-jsx-a11y의 권장 규칙을 추가했다.
이후 동일한 코드에서 더 많은 접근성 문제를 오류로 발견할 수 있었다.
하지만 Lint 규칙을 추가했다고 해서 모든 접근성 문제가 해결되는 것은 아니다.
예를 들어 특정 버튼이 실제로 보이는지, 스크린 리더에서 올바른 순서로 읽히는지 등은 소스코드 분석만으로 판단하기 어렵다.
따라서 실제 렌더링 결과에 대한 테스트도 필요하다.
Form과 Error Message
입력 양식에서는 Label과 오류 메시지의 연결이 중요하다.
예를 들어 이메일 입력 필드에 잘못된 값을 입력했다고 가정해 보자.
화면 아래에 빨간색 오류 메시지를 표시하는 것만으로는 충분하지 않을 수 있다.
스크린 리더 사용자가 해당 입력 필드에 오류가 발생했다는 사실을 알 수 있어야 한다.
저자는 다음 속성을 이용하는 방식을 소개한다.
aria-invalidaria-describedby
aria-invalid는 해당 입력값이 올바르지 않다는 상태를 나타낸다.
aria-describedby는 입력 요소와 설명 또는 오류 메시지를 연결한다.
예를 들어 입력 필드에 오류가 발생했다면 해당 오류 메시지의 ID를 aria-describedby로 연결할 수 있다.
이렇게 하면 스크린 리더가 입력 필드와 관련된 오류 설명을 함께 제공할 수 있다.
저자는 React의 useId()를 이용해 Label과 오류 메시지의 ID를 생성하는 방법을 소개한다.
이는 같은 컴포넌트를 여러 번 사용했을 때 ID가 충돌하는 문제를 줄이기 위한 것이다.
인증 과정과 인지적 부담
WCAG 2.2의 Accessible Authentication 기준은 로그인 과정에서도 중요하다.
예를 들어 사용자가 인증 앱에서 생성된 숫자를 확인한 뒤 웹사이트에 입력해야 하는 상황을 생각할 수 있다.
일부 서비스에서는 인증번호 여섯 자리를 각각 다른 입력 필드에 입력하도록 요구한다.
이 과정에서 붙여넣기 기능을 제한하면 사용자가 인증번호를 기억하고 수동으로 입력해야 할 수 있다.
저자는 이러한 문제를 피하기 위해 인증번호를 하나의 입력 필드로 처리하도록 안내한다.
또한 다음 속성을 활용할 수 있다.
autocomplete="one-time-code"
이 속성은 브라우저와 운영체제가 일회용 인증번호 입력을 지원하는 데 사용된다.
비밀번호 입력에서도 붙여넣기와 자동완성을 불필요하게 차단하지 않는 것이 중요하다.
Passkey와 WebAuthn을 이용한 인증 역시 접근성 측면에서 검토할 수 있는 방식이다.
다만 Passkey를 제공한다는 사실만으로 모든 인증 흐름이 자동으로 WCAG를 충족하는 것은 아니다.
실제 인증 화면의 구조와 대체 인증 수단을 함께 확인해야 한다.
Modal의 포커스 관리
Modal은 접근성 문제가 발생하기 쉬운 UI다.
예를 들어 사용자가 버튼을 눌러 설정 Modal을 열었다고 가정해 보자.
이때 키보드 포커스가 Modal 안으로 이동해야 한다.
Modal이 열린 상태에서는 일반적으로 배경 화면의 조작이 제한돼야 한다.
또한 Escape 키로 닫을 수 있어야 하고, Modal을 닫은 뒤에는 원래 버튼으로 포커스가 돌아와야 한다.
저자는 기본 HTML의 <dialog> 요소와 showModal()을 사용하는 방식을 소개한다.
브라우저의 기본 Dialog 기능을 활용하면 이러한 동작의 상당 부분을 직접 구현하지 않아도 된다.
하지만 React에서 Dialog의 열림 상태를 관리할 때는 실제 DOM 상태와 React 상태가 일치하도록 처리해야 한다.
또한 테스트 환경에도 제약이 존재한다.
저자는 jsdom에서 showModal()이 지원되지 않는 문제를 확인했다.
따라서 이러한 기능은 실제 브라우저를 사용하는 Playwright 테스트에서 검증하는 것이 적합하다.
Next.js 페이지 전환과 포커스
저자는 Next.js App Router의 접근성 문제도 설명한다.
SPA에서는 사용자가 다른 페이지로 이동하더라도 전체 HTML 문서가 다시 로딩되지 않을 수 있다.
화면의 콘텐츠는 변경됐지만 키보드 포커스는 이전 위치에 남아 있을 수 있다.
이 경우 스크린 리더 사용자는 페이지가 변경됐다는 사실을 이해하기 어려울 수 있다.
Next.js에는 페이지 제목을 알리는 Route Announcer가 존재한다.
하지만 저자가 확인한 Next.js 16.4 환경에서는 페이지 제목의 변경을 알리는 것과 키보드 포커스를 새로운 콘텐츠로 이동하는 것이 서로 다른 작업이었다.
따라서 사용자가 새로운 페이지로 이동했을 때 필요한 경우 메인 제목에 포커스를 이동하도록 구현할 수 있다.
특히 Cache Components를 사용하는 환경에서는 이전 페이지가 DOM에서 완전히 제거되지 않을 수 있다.
따라서 DOM에서 첫 번째 제목을 단순히 선택하는 방식은 잘못된 요소를 가리킬 수 있다.
저자는 실제로 화면에 표시된 제목인지 확인한 뒤 포커스를 이동하는 방법을 소개한다.
이러한 문제는 브라우저가 페이지 전체를 새로 로딩하는 방식과 클라이언트에서 UI만 변경하는 방식의 접근성 차이를 보여준다.
접근성 테스트의 여러 계층
저자는 접근성을 하나의 도구로 완전히 검사할 수 없다고 설명한다.
대신 여러 테스트를 조합한다.
첫 번째는 Lint다.
소스코드에서 발견할 수 있는 접근성 문제를 검사한다.
두 번째는 React Testing Library다.
사용자가 실제로 접근할 수 있는 역할과 이름을 기준으로 컴포넌트를 테스트한다.
예를 들어 getByRole()을 사용해 Button이나 입력 필드를 조회할 수 있다.
세 번째는 Playwright와 axe-core다.
실제 브라우저에서 렌더링된 DOM을 검사한다.
여기서 주의해야 할 부분은 axe-core의 WCAG 2.2 관련 규칙이 기본 설정에서 모두 활성화돼 있다고 가정해서는 안 된다는 점이다.
저자는 Target Size 규칙을 확인하기 위해 wcag22aa Tag를 명시적으로 사용했다.
기본 스캔에서는 발견하지 못했던 문제가 해당 설정에서 발견됐다.
그러나 자동화 테스트를 통과했다고 해서 실제 사용자에게 완전한 접근성이 보장되는 것은 아니다.
예를 들어 스크린 리더가 긴 페이지를 탐색할 때 실제로 이해하기 쉬운 순서로 읽히는지는 사람이 직접 확인해야 할 수 있다.
따라서 키보드 사용자와 스크린 리더 사용자의 실제 경험을 검증하는 과정도 필요하다.
접근성을 컴포넌트 설계의 일부로 다루기
이 글의 핵심은 WCAG 항목을 개별적으로 암기하는 것이 아니다.
공통 컴포넌트를 설계할 때부터 올바른 의미와 조작 방식을 포함하는 것이다.
예를 들어 접근 가능한 Button과 Dialog, Input 컴포넌트가 이미 준비돼 있다면 이후 새로운 화면을 만들 때 같은 문제를 반복해서 해결할 필요가 줄어든다.
반대로 접근성 문제가 있는 공통 컴포넌트를 사용하면 여러 페이지에서 동일한 문제가 발생할 수 있다.
따라서 저자는 개별 페이지를 수정하는 방식보다 디자인 시스템과 공통 컴포넌트, 자동화 테스트에 접근성 요구사항을 포함하는 방식을 제안한다.
이번 글은 React와 Next.js에서 자주 발생하는 접근성 문제를 실제 TypeScript 코드와 테스트 결과로 설명한다.
특히 WCAG 2.2의 새로운 기준이 컴포넌트 구현과 어떤 관계가 있는지 이해하는 데 참고할 만하다.