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

기준 시점: 2026-10-18 06:00 KST
조사 범위: 2026-10-17 06:00 ~ 2026-10-18 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
React·Next.js·Vercel, Vue·Nuxt, Angular, Svelte·SvelteKit, Astro, Remix의 공식 발표와 릴리스 정보를 확인했지만, 이번 24시간에 새롭게 공개된 주요 프레임워크 기능이나 렌더링 API 변경은 확인하지 못했다.
JavaScript·TypeScript와 주요 프론트엔드 빌드 도구에서도 별도로 소개할 만한 중요한 신규 발표는 확인하지 못했다.
Vercel에서는 AI Gateway의 세션별 요청 분석 기능을 공개했지만, React나 Next.js 자체의 변경은 아니다. 해당 기능은 백엔드·인프라 섹션에서 다룬다.
이전에 소개한 Astro 7.4와 Void 등 조사 범위 이전의 발표는 반복하지 않았다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
W3C, WHATWG, TC39, MDN, Chrome Developers·Chromium, Mozilla·Firefox, WebKit·Safari의 공개 정보를 확인했지만, 이번 조사 범위에 해당하는 주요 웹 표준 변경이나 새로운 브라우저 기능의 정식 출시 발표는 확인하지 못했다.
브라우저 개발 버전의 개별 빌드와 소규모 수정은 새로운 기능 발표와 구분했다.
특히 WebKit은 조사 기간에도 개발 빌드가 계속 생성됐지만, 개별 빌드의 생성 사실만으로 새로운 CSS 기능이나 웹 API가 안정화됐다고 판단할 수는 없다.
3️⃣ 백엔드·인프라
Vercel AI Gateway, 세션별 로그 분석 지원 — 대화 전체의 캐시 적중률·비용·모델 전환 경로 확인
- 발표일: 2026-10-17 — 정확한 게시 시각 미확인
- 적용 대상: Vercel AI Gateway
- 발표 주체: Vercel
- 원문: https://vercel.com/changelog/ai-gateway-logs-now-show-session-affinity
Vercel이 AI Gateway의 로그에서 여러 요청을 하나의 대화 세션으로 묶어 분석하는 Sessions View를 추가했다.
AI Gateway는 애플리케이션이 여러 AI 모델 제공업체에 접근할 때 사용할 수 있는 통합 인터페이스다.
개발자는 모델 제공업체마다 별도의 통신 코드를 구현하는 대신 Gateway를 통해 모델을 호출할 수 있다.
이러한 구조에서는 개별 API 요청의 성공 여부뿐 아니라 여러 요청으로 구성된 하나의 작업이 어떻게 실행됐는지 파악하는 것이 중요하다.
예를 들어 AI 코딩 에이전트가 하나의 작업을 수행하면서 모델에 30번의 요청을 보냈다고 가정해 보자.
각 요청은 서로 다른 입력과 출력 토큰을 사용하고, 일부 요청은 이전 대화 내용을 다시 전달할 수 있다.
모델이나 제공업체가 중간에 변경될 수도 있다.
이때 요청별 로그만 확인하면 전체 작업에 얼마의 비용이 발생했는지, 캐시가 얼마나 효과적으로 사용됐는지, 모델 제공업체가 언제 변경됐는지 파악하기 어렵다.
이번 업데이트는 개별 요청이 아니라 하나의 세션 전체를 관찰하는 기능을 제공한다.
Sessions View에서 확인할 수 있는 정보
새로운 화면은 관련된 AI 요청들을 하나의 세션으로 묶는다.
각 세션에서는 다음 정보를 확인할 수 있다.
- 요청을 실행한 코딩 에이전트 또는 애플리케이션
- 세션 전체의 비용
- 사용된 모델
- 요청을 처리한 모델 제공업체
- 전체 대화에서 캐시된 입력의 비율
- 대화 차례(Turn) 수
- 세션의 실행 시간
- 모델 및 제공업체의 전환 경로
Vercel은 Claude Code, Codex, Cursor 같은 코딩 에이전트를 예로 제시했다.
예를 들어 하나의 코딩 에이전트가 코드베이스를 분석한 뒤 여러 파일을 수정하고 테스트를 수행하는 상황을 생각할 수 있다.
에이전트는 각 단계에서 모델을 여러 번 호출한다.
기존에는 이러한 호출을 각각 분석해야 했다.
이제는 하나의 작업을 구성하는 요청들의 관계를 세션 단위로 확인할 수 있다.
특히 어떤 모델이 어느 시점에 사용됐는지와 각 요청에 걸린 시간을 함께 볼 수 있다.
다만 Sessions View가 실제 코드 수정의 정확성이나 테스트 통과 여부를 자동으로 검증하는 기능은 아니다.
AI 요청의 실행 경로와 비용, 캐시 사용 상태를 관찰하는 기능이다.
Session Affinity의 동작
이번 기능을 이해하려면 Session Affinity를 알아야 한다.
Session Affinity는 같은 대화에 속하는 요청을 가능하면 동일한 모델 제공업체로 전달하는 방식이다.
예를 들어 하나의 대화에서 첫 번째 요청이 특정 제공업체의 모델로 처리됐다고 가정해 보자.
두 번째 요청에도 이전 대화 기록이 포함될 수 있다.
동일한 제공업체에서 두 번째 요청을 처리하면 첫 번째 요청에 사용했던 입력의 일부가 해당 제공업체의 캐시에 남아 있을 수 있다.
이 경우 이전 대화 내용을 처음부터 다시 처리하는 비용을 줄일 가능성이 있다.
반대로 매 요청을 서로 다른 제공업체로 보내면 기존 제공업체에 저장된 캐시를 활용하지 못할 수 있다.
Vercel AI Gateway는 이러한 문제를 줄이기 위해 동일한 세션의 요청을 이전 요청을 처리했던 제공업체에 연결한다.
Vercel은 이번 업데이트에서 Session Affinity의 라우팅 결과를 로그에서 확인할 수 있도록 했다.
따라서 개발자는 캐시 적중률이 낮은 세션에서 모델 제공업체가 반복적으로 변경됐는지 확인할 수 있다.
다만 같은 제공업체를 사용한다고 해서 모든 요청에서 캐시가 반드시 적중하는 것은 아니다.
실제 캐시 사용 여부는 입력 내용, 제공업체의 캐시 정책, 모델과 요청 설정 등에 영향을 받는다.
Cache Hit Rate와 비용의 관계
Sessions View에서는 Cache Hit Rate를 확인할 수 있다.
이는 전체 입력 가운데 캐시를 통해 처리된 부분의 비율을 나타낸다.
예를 들어 코딩 에이전트가 매 요청에 긴 시스템 지침과 프로젝트 문맥을 포함한다고 가정해 보자.
첫 번째 요청에서는 해당 입력을 새롭게 처리해야 할 수 있다.
하지만 이후 요청에서도 동일한 내용이 반복된다면 제공업체가 해당 내용을 캐시에서 재사용할 수 있다.
이 경우 모델 제공업체가 적용하는 캐시 요금 정책에 따라 입력 처리 비용이 줄어들 수 있다.
다만 캐시 적중률만으로 실제 비용 절감액을 계산할 수는 없다.
모델별 입력 토큰 가격, 캐시 읽기 가격과 캐시 생성 비용이 다를 수 있기 때문이다.
따라서 실제 비용을 분석하려면 다음 정보를 함께 확인해야 한다.
- 캐시 적중률
- 전체 입력 및 출력 토큰
- 모델별 가격
- 요청별 모델 제공업체
- 실제 세션 비용
이번 업데이트는 이러한 정보를 세션 단위에서 함께 확인할 수 있도록 한다.
세션의 요청 경로 분석
개발자는 개별 세션을 열어 요청의 실행 경로(Path)를 확인할 수 있다.
각 Turn에는 사용된 모델과 처리 시간이 표시된다.
또한 해당 요청을 처리한 제공업체와 이전에 요청을 처리했던 제공업체도 확인할 수 있다.
예를 들어 다음과 같은 실행 흐름을 생각할 수 있다.
- 첫 번째 요청이 제공업체 A에서 처리된다.
- 두 번째 요청도 제공업체 A로 전달된다.
- 세 번째 요청에서 제공업체 B로 전환된다.
- 이후 요청이 제공업체 B에서 처리된다.
이 경우 개발자는 세 번째 요청을 기준으로 캐시 적중률과 비용에 어떤 변화가 발생했는지 살펴볼 수 있다.
다만 제공업체 변경이 반드시 잘못된 라우팅이라는 의미는 아니다.
모델 가용성, 요청 조건이나 제공업체의 응답 상태 등으로 인해 다른 제공업체가 선택될 수 있다.
따라서 Sessions View는 변경 자체를 오류로 판단하기보다 라우팅 결과와 비용 및 처리 시간 사이의 관계를 확인하는 도구로 이해해야 한다.
세션 필터링
새로운 화면에서는 다음 조건으로 세션을 필터링할 수 있다.
- 대화 차례 수
- 클라이언트
- 모델
- 모델 제공업체
- 인증 방식
- 비용
기본적으로 한 번의 요청으로 종료된 Single-turn Session은 숨겨진다.
해당 세션을 확인하려면 최소 Turn 수 필터를 해제해야 한다.
이는 여러 요청으로 구성된 대화를 우선 분석하기 위한 UI 선택이다.
하지만 단일 요청의 기록이 존재하지 않거나 삭제된다는 의미는 아니다.
명시적인 세션 식별자
애플리케이션은 세션을 직접 식별할 수도 있다.
Vercel은 두 가지 방법을 제공한다.
첫 번째는 providerOptions.gateway에 동일한 sessionId를 전달하는 방식이다.
두 번째는 HTTP 요청에 동일한 x-session-id 헤더를 포함하는 방식이다.
여러 요청에서 같은 값을 전달하면 Gateway가 해당 요청들을 동일한 대화로 묶을 수 있다.
예를 들어 사용자가 하나의 채팅방에서 여러 메시지를 보낸다면 해당 채팅방의 대화 식별자를 세션 ID로 사용할 수 있다.
다만 세션 ID가 동일하다는 사실만으로 사용자 인증이나 접근 권한이 보장되는 것은 아니다.
서로 다른 사용자의 데이터가 섞이지 않도록 세션 식별자를 생성하고 관리하는 방식은 애플리케이션에서 별도로 설계해야 한다.
명시적인 세션 ID가 없는 요청에 대해서는 Gateway가 대화의 시작 부분을 바탕으로 Fingerprint를 생성해 요청을 그룹화한다.
이렇게 분류된 세션은 Fingerprinted Session으로 표시된다.
이 방식은 개발자가 별도의 세션 ID를 제공하지 않은 경우에도 분석을 가능하게 하지만, 애플리케이션이 명시적인 대화 식별자를 전달하는 방식과는 구분된다.
신규 기능의 적용 범위
Session Affinity는 텍스트 및 채팅 요청에서 기본적으로 적용된다.
이번 발표는 새로운 모델이나 캐싱 알고리즘의 출시가 아니다.
또한 실제 비용이 특정 비율만큼 감소했다는 정량적인 Benchmark도 제시하지 않았다.
기존 Gateway의 세션 라우팅과 캐시 사용 결과를 개발자가 관찰할 수 있도록 로그 분석 기능을 확대한 업데이트다.
특히 하나의 사용자 요청이 여러 모델 호출로 이어지는 코딩 에이전트와 장시간 대화형 애플리케이션에서 전체 작업 단위의 비용과 실행 경로를 파악할 수 있도록 한다.
4️⃣ 개발 도구 및 보안
Cloudflare Local Explorer, Cron Trigger 로컬 테스트 지원 — 배포 없이 예약 실행 함수를 호출하고 결과 확인
- 발표일: 2026-10-17 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare Workers, Cloudflare CLI, Wrangler, Cloudflare Vite Plugin
- 원문: https://developers.cloudflare.com/changelog/post/2026-10-17-local-explorer-cron-triggers/
Cloudflare가 Local Explorer에서 Cron Trigger를 직접 실행할 수 있는 기능을 추가했다.
Cron Trigger는 정해진 일정에 따라 서버 코드를 실행하는 기능이다.
예를 들어 다음과 같은 작업에 사용할 수 있다.
- 매일 오전 6시 데이터 수집
- 한 시간마다 외부 API 상태 확인
- 매일 밤 통계 데이터 집계
- 주기적인 캐시 갱신
- 일정한 간격으로 데이터베이스 정리
Cloudflare Workers에서는 이러한 작업을 scheduled() Handler로 구현한다.
일반적인 HTTP 요청은 fetch() Handler가 처리하지만, 예약된 작업은 scheduled() Handler가 처리한다.
두 Handler는 실행되는 계기가 다르다.
HTTP 요청은 클라이언트가 서버에 요청을 보낼 때 실행된다.
반면 Cron Trigger는 Cloudflare에 등록된 일정에 따라 실행된다.
문제는 이러한 예약 작업을 개발 환경에서 테스트하기가 일반적인 HTTP API보다 번거로웠다는 점이다.
기존 로컬 테스트의 문제
일반적인 HTTP API는 개발 서버를 실행한 뒤 브라우저나 API 클라이언트에서 특정 URL을 호출하면 테스트할 수 있다.
그러나 scheduled() Handler는 일반적인 HTTP 요청을 처리하는 함수가 아니다.
따라서 예약 작업을 테스트하려면 Wrangler가 제공하는 별도의 테스트 엔드포인트를 호출해야 했다.
이 과정에서 개발자는 HTTP 요청을 직접 구성하거나 별도의 스크립트를 작성해야 할 수 있었다.
또한 실제 운영 환경에서 사용하는 Cron 표현식과 테스트에 사용하는 실행 시점을 연결해야 했다.
예를 들어 매일 오전 6시에 실행되는 작업을 개발 중이라고 가정해 보자.
실제 오전 6시까지 기다리면서 테스트하는 방식은 비효율적이다.
반대로 임의의 HTTP 요청으로 비슷한 함수를 호출하면 실제 scheduled() Handler가 전달받는 실행 문맥과 차이가 생길 수 있다.
이번 Local Explorer 업데이트는 예약 작업의 실행을 로컬 개발 환경에서 직접 시뮬레이션할 수 있는 인터페이스를 제공한다.
Local Explorer에서 Cron Trigger 실행
Local Explorer는 Cloudflare Workers의 로컬 개발 환경을 브라우저에서 확인하고 조작할 수 있는 도구다.
이번 업데이트 이후 다음 개발 환경에서 사용할 수 있다.
cf devwrangler dev- Cloudflare Vite Plugin
개발 서버를 실행한 뒤 다음 경로로 접근한다.
/cdn-cgi/local/explorer
예를 들어 로컬 개발 서버가 8787 포트에서 실행된다면 다음 주소를 사용할 수 있다.
http://localhost:8787/cdn-cgi/local/explorer
Vite를 사용한다면 실제 개발 서버의 포트가 5173일 수 있다.
따라서 고정된 포트를 가정하기보다 현재 실행 중인 개발 서버의 주소를 사용해야 한다.
Local Explorer에서 Cron Triggers 메뉴를 선택하면 예약 작업을 직접 호출할 수 있다.
제공되는 테스트 기능
새로운 인터페이스에서는 다음 작업을 수행할 수 있다.
| 기능 | 설명 |
|---|---|
| 기존 Cron 표현식 실행 | Worker에 설정된 Cron 표현식으로 예약 실행 시뮬레이션 |
| 임의 Cron 표현식 작성 | 설정 파일을 변경하지 않고 별도의 Cron 표현식 테스트 |
| 예약 실행 시점 지정 | 특정 시점을 기준으로 Handler 실행 |
| 실행 결과 확인 | Handler의 실행 결과와 상태 확인 |
noRetry() 상태 확인 | 재시도 금지 여부 확인 |
| 로그와 Trace 확인 | 실행 과정의 로그 및 추적 정보 확인 |
예를 들어 운영 환경에서는 매일 한 번 실행되는 작업이라도 로컬 환경에서는 여러 번 호출하며 결과를 확인할 수 있다.
또한 실제 운영 일정에 등록되지 않은 Cron 표현식을 사용해 테스트할 수도 있다.
이 과정에서 중요한 점은 테스트용 설정이 운영 환경의 Cron 설정을 변경하지 않는다는 사실이다.
Local Explorer에서 작성한 임의 Cron 표현식과 예약 실행 시점은 브라우저에 유지된다.
다음 설정 파일을 변경하지 않는다.
cloudflare.config.ts- Wrangler 설정 파일
- 실제 배포된 Cron Trigger
따라서 운영 중인 Worker의 예약 실행 주기에 영향을 주지 않고 로컬에서 다양한 실행 조건을 시험할 수 있다.
로컬 Cron 스케줄은 자동 실행되지 않는다
이번 기능에는 중요한 제한사항이 있다.
Local Explorer에서 Cron 표현식을 지정했다고 해서 로컬 개발 서버가 해당 일정에 따라 자동으로 작업을 실행하는 것은 아니다.
실제 운영 환경에서는 Cloudflare가 등록된 Cron 표현식에 따라 Worker를 호출한다.
반면 로컬 개발 환경에서는 개발자가 Local Explorer 또는 API를 통해 명시적으로 실행을 요청해야 한다.
따라서 이번 기능은 운영 환경의 스케줄러를 완전히 재현하는 기능이라기보다 예약 실행 Handler를 특정 조건으로 호출하는 테스트 도구다.
실제 예약 실행이 정확한 시점에 이루어지는지, 운영 환경에서 여러 Worker가 어떻게 실행되는지까지 자동으로 검증하는 것은 아니다.
로그와 Trace 확인
로컬 개발 서버에서 Observability가 활성화돼 있다면 예약 작업의 로그와 Trace도 확인할 수 있다.
Observability는 프로그램이 실행되는 동안 어떤 일이 발생하는지 외부에서 관찰할 수 있도록 하는 기능을 뜻한다.
대표적인 구성요소는 다음과 같다.
- 로그(Log): 프로그램이 기록한 실행 메시지
- Trace: 요청이나 작업이 여러 단계를 거쳐 실행되는 흐름
- Metric: 실행 시간, 오류 수와 같은 수치
예를 들어 예약 작업이 외부 API를 호출한 뒤 데이터를 저장한다고 가정해 보자.
작업이 실패했다면 개발자는 로그에서 오류 메시지를 확인할 수 있다.
Trace를 이용하면 어떤 단계에서 시간이 소비되거나 오류가 발생했는지 분석할 수 있다.
다만 공식 발표에서는 실행 중인 해당 개발 세션에서 Observability가 활성화돼 있어야 전체 로그와 Trace를 확인할 수 있다고 명시한다.
따라서 Local Explorer를 열었다는 사실만으로 모든 실행 기록이 자동으로 수집되는 것은 아니다.
Local Explorer API
브라우저 인터페이스 외에 API도 제공된다.
기본 엔드포인트는 다음과 같다.
POST /cdn-cgi/local/explorer/api/local/scheduled
호출할 Worker는 Query Parameter로 지정한다.
worker=my-worker
요청 본문에는 다음 값을 전달할 수 있다.
cron: 실행할 Cron 표현식scheduled_time: 선택적인 예약 실행 시점
예를 들어 0 * * * *는 매시간 정각에 실행되는 Cron 표현식이다.
Cloudflare 공식 예제에서는 이러한 표현식을 API에 전달해 지정된 Worker의 scheduled() Handler를 호출한다.
API가 제공된다는 점은 브라우저 UI만 사용할 수 있는 기능과 차이가 있다.
개발자는 자동화 스크립트나 AI 코딩 에이전트에서도 동일한 동작을 실행할 수 있다.
예를 들어 코딩 에이전트가 예약 작업의 코드를 수정한 뒤 API를 호출하고, 결과 로그를 확인하는 작업 흐름을 구성할 수 있다.
그러나 API 호출이 성공했다고 해서 실제 운영 환경에서 예약 작업이 반드시 정상적으로 실행된다는 의미는 아니다.
로컬 환경과 운영 환경의 네트워크, 권한, 데이터베이스 연결 및 환경 변수에 차이가 있을 수 있기 때문이다.
또한 예약 작업 자체가 외부 시스템을 변경하는 경우에는 테스트 환경의 데이터와 운영 데이터를 구분해야 한다.
이번 업데이트는 Cron Trigger의 실행 문법이나 Cloudflare의 운영 스케줄러 자체를 변경하지 않는다.
기존 예약 실행 함수를 로컬 개발 환경에서 더 쉽게 호출하고 관찰할 수 있도록 개발 및 테스트 도구를 확장한 기능이다.
이번 조사 범위에서 개발 도구 및 보안과 관련해 위 항목 외에 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
GitHub의 공식 Changelog에서는 10월 17일에 새로 공개된 주요 보안 및 개발 도구 변경을 확인하지 못했다.
Node.js 공식 릴리스 채널에서도 이번 기간에 해당하는 주요 신규 릴리스는 확인하지 못했다.
이전에 다룬 GitHub Actions Lockfile, Dependabot 보안 경고 처리 변경, AI Scan 정식 출시, Cloudflare의 보안 규칙 업데이트 등은 반복하지 않았다.
Vercel AI Gateway에는 새로운 모델을 연결하는 발표도 있었지만, 기존 Gateway의 사용 방식이나 웹 개발 플랫폼의 동작을 실질적으로 변경하는 내용보다 모델 선택지 확대에 초점이 맞춰져 있어 별도 항목으로 포함하지 않았다.
📚 추천 글
Experimental soft navigation metrics now available in CrUX
- 저자: Annie Sullivan / Google Chrome Team
- 게시일: 2026-10-13
- 원문: https://web.dev/blog/crux-experimental-soft-navs
- 관련 문서: https://developer.chrome.com/docs/web-platform/soft-navigations
Google Chrome 팀이 Single-Page Application(SPA)의 클라이언트 측 페이지 전환을 Core Web Vitals에 포함하는 새로운 측정 방식을 설명한 글이다.
React, Vue, Angular 같은 프레임워크를 사용하는 웹 애플리케이션에서는 페이지가 변경되더라도 브라우저가 HTML 문서를 새로 다운로드하지 않는 경우가 많다.
예를 들어 사용자가 쇼핑몰의 상품 목록에서 특정 상품을 클릭했다고 가정해 보자.
일반적인 Multi-Page Application에서는 브라우저가 새로운 HTML 문서를 요청한다.
반면 SPA에서는 JavaScript가 새로운 데이터를 가져오고 화면의 일부를 변경한다.
주소창의 URL은 변경될 수 있지만 실제로 새로운 HTML 문서가 로딩되지는 않는다.
이러한 전환을 Soft Navigation이라고 한다.
문제는 기존 Core Web Vitals 측정 방식이 이러한 전환을 충분히 반영하지 못했다는 점이다.
기존 Core Web Vitals의 측정 한계
Core Web Vitals는 실제 사용자가 웹사이트를 이용하면서 경험하는 성능을 측정하기 위한 지표다.
대표적으로 다음 세 가지가 있다.
| 지표 | 측정 대상 |
|---|---|
| LCP | 주요 콘텐츠가 화면에 표시되는 속도 |
| INP | 사용자 입력 이후 화면이 반응하는 속도 |
| CLS | 예기치 않은 레이아웃 변경으로 인한 시각적 불안정성 |
일반적인 웹페이지에서는 사용자가 새로운 HTML 문서를 요청할 때마다 페이지 로딩이 발생한다.
브라우저는 해당 로딩에 대해 LCP를 측정한다.
이후 사용자의 입력과 레이아웃 변경을 측정한다.
하지만 SPA에서는 한 번 페이지가 로딩된 이후에도 여러 화면이 변경될 수 있다.
기존 CrUX(Chrome User Experience Report)는 이러한 Soft Navigation을 별도의 페이지 로딩으로 충분히 구분하지 못했다.
예를 들어 사용자가 다음 순서로 이동한다고 가정해 보자.
- 상품 목록에 접속한다.
- 상품 상세 페이지로 이동한다.
- 장바구니로 이동한다.
- 주문 페이지로 이동한다.
SPA에서는 네 단계가 하나의 문서 안에서 처리될 수 있다.
기존 측정에서는 첫 번째 페이지 로딩이 LCP의 주요 측정 대상이었다.
반면 이후 페이지 전환에서 발생하는 화면 변경은 각각 독립적인 페이지 로딩으로 측정되지 않았다.
INP와 CLS도 긴 SPA 세션 전체의 영향을 받았다.
그 결과 하나의 애플리케이션 안에서 서로 다른 화면의 성능 문제를 정확하게 구분하기 어려웠다.
세 가지 새로운 실험 지표
Chrome 팀은 Soft Navigation을 포함하는 실험적 Core Web Vitals를 CrUX에 추가했다.
새롭게 제공되는 지표는 다음과 같다.
| 지표 | 데이터 필드 |
|---|---|
| Soft Navigation 포함 LCP | experimental_largest_contentful_paint_with_softnavs |
| Soft Navigation 포함 INP | experimental_interaction_to_next_paint_with_softnavs |
| Soft Navigation 포함 CLS | experimental_cumulative_layout_shift_with_softnavs |
이 지표들은 일반적인 페이지 로딩과 Soft Navigation을 함께 측정한다.
그러나 기존 지표를 즉시 대체하는 것은 아니다.
현재는 실험적 지표로 별도로 제공된다.
Google이 공식적으로 사용하는 기존 Core Web Vitals 점수는 그대로 유지된다.
개발자는 새로운 측정 결과를 기존 결과와 비교할 수 있다.
Soft Navigation 이후의 LCP 측정
가장 큰 변화 가운데 하나는 LCP다.
기존에는 SPA가 처음 로딩될 때의 LCP가 측정됐다.
하지만 사용자가 다른 화면으로 이동했을 때 새롭게 표시되는 주요 콘텐츠는 별도의 페이지 로딩으로 측정되지 않았다.
새로운 방식에서는 Soft Navigation 이후의 주요 콘텐츠 표시 시간도 측정한다.
예를 들어 사용자가 상품 목록에서 상세 화면으로 이동했다고 가정해 보자.
이미 애플리케이션의 JavaScript와 CSS 일부가 다운로드된 상태라면 상세 페이지 전환은 초기 페이지 로딩보다 빠를 수 있다.
반대로 상세 페이지가 매우 큰 이미지를 다운로드하거나 복잡한 JavaScript를 실행한다면 전환이 느려질 수도 있다.
이번 지표는 이러한 경험을 포함한다.
하지만 Soft Navigation을 포함한 LCP 점수가 개선됐다고 해서 SPA의 코드 실행 성능이 실제로 개선됐다는 의미는 아니다.
이전에는 측정하지 않던 빠른 후속 페이지 전환이 새롭게 포함되면서 전체 통계가 좋아질 수 있기 때문이다.
즉 코드가 변경되지 않았더라도 측정 대상이 달라져 점수가 변경될 수 있다.
INP를 페이지별로 구분
INP에서도 중요한 변화가 있다.
기존 SPA에서는 사용자가 여러 화면을 탐색하는 동안 발생한 상호작용이 하나의 긴 세션에 포함됐다.
예를 들어 상품 목록에서는 모든 버튼이 빠르게 반응하지만 주문 페이지에서만 특정 버튼이 느리게 반응한다고 가정해 보자.
기존 측정에서는 전체 세션의 INP가 주문 페이지의 느린 상호작용에 영향을 받을 수 있었다.
하지만 실제로 어떤 URL에서 문제가 발생했는지 분석하기는 어려웠다.
새로운 지표는 Soft Navigation을 기준으로 상호작용을 개별 페이지에 연결한다.
따라서 주문 화면에서 발생한 입력 지연을 상품 목록의 성능과 구분할 수 있다.
다만 Soft Navigation을 인식하는 방식은 브라우저의 구현과 실험적 API에 영향을 받는다.
현재는 모든 브라우저에서 동일한 방식으로 데이터를 제공하는 안정적인 웹 표준이라고 볼 수 없다.
CLS도 페이지 전환 단위로 측정
CLS는 사용자가 의도하지 않은 레이아웃 이동을 측정한다.
예를 들어 이미지의 크기를 지정하지 않아 이미지가 로딩된 이후 아래쪽 콘텐츠가 밀려나는 경우가 있다.
긴 SPA 세션에서는 여러 화면에서 발생한 레이아웃 변경이 동일한 문서의 측정값에 영향을 줄 수 있다.
새로운 지표에서는 Soft Navigation에 따라 CLS를 구분한다.
따라서 특정 화면에서 발생하는 레이아웃 문제를 해당 페이지의 URL과 연결할 수 있다.
이러한 구분은 일반적인 Multi-Page Application에서 새로운 문서가 로딩될 때마다 측정이 분리되는 방식과 SPA의 측정 방식을 더 가깝게 만든다.
실제 CrUX 데이터에서 나타난 차이
Google은 실제 CrUX 데이터를 이용해 새로운 측정 방식의 영향을 분석했다.
공식 분석에 따르면 전체 웹사이트 가운데 대부분은 Soft Navigation을 거의 사용하지 않는다.
약 66~75%의 Origin에서는 Soft Navigation이 전체 페이지 조회의 5% 미만을 차지했다.
반면 방문자가 많은 웹사이트에서는 SPA의 사용 비중이 더 높았다.
상위 1,000개 Origin 가운데 Soft Navigation이 전체 조회의 절반 이상을 차지하는 비율은 다음과 같았다.
| 환경 | Soft Navigation 비중이 50%를 초과하는 Origin |
|---|---|
| Android | 약 35% |
| Desktop | 약 42% |
이 차이는 일반적인 웹사이트 전체를 대상으로 할 때와 대규모 웹 애플리케이션을 대상으로 할 때 Soft Navigation의 중요성이 달라진다는 점을 보여준다.
블로그나 일반적인 문서 사이트에서는 새로운 지표가 기존 지표와 거의 같을 수 있다.
반면 하나의 문서 안에서 여러 화면을 탐색하는 대규모 애플리케이션에서는 결과가 크게 달라질 수 있다.
LCP 통과율의 변화
Google은 Soft Navigation이 전체 탐색의 75~100%를 차지하는 Android Origin들을 별도로 분석했다.
기존 측정에서 좋은 LCP를 기록한 Origin은 약 26%였다.
새로운 측정에서는 약 87%로 증가했다.
매우 큰 차이다.
하지만 이 결과를 SPA의 성능이 실제로 세 배 이상 개선됐다는 의미로 해석해서는 안 된다.
브라우저는 기존에 다운로드한 JavaScript와 CSS, 이미지를 재사용할 수 있다.
따라서 후속 화면 전환은 일반적으로 초기 로딩보다 빠를 수 있다.
기존 CrUX에서는 이러한 빠른 후속 전환이 측정 대상에서 빠져 있었다.
새로운 지표는 이를 포함하므로 더 많은 빠른 전환이 전체 결과에 반영된다.
즉 실제 사용자가 경험한 모든 화면 전환을 더 폭넓게 측정하면서 통계가 변경된 것이다.
INP와 CLS의 변화
INP와 CLS에서도 비슷한 변화가 나타났다.
SPA를 많이 사용하는 사이트에서는 INP가 개별 화면 기준으로 측정되면서 통과율이 개선됐다.
일부 그룹에서는 Android의 좋은 INP 통과율이 대략 두 배로 증가했다.
하지만 이러한 개선 이후에도 SPA 중심 웹사이트의 INP는 일반적인 Multi-Page Application보다 낮은 수준을 보였다.
따라서 새로운 측정 방식이 SPA의 JavaScript 실행 비용을 없애는 것은 아니다.
복잡한 클라이언트 렌더링과 많은 메인 스레드 작업은 여전히 사용자 입력을 지연시킬 수 있다.
CLS도 긴 세션 전체에 걸쳐 누적되던 레이아웃 변경을 개별 페이지 전환으로 분리하면서 결과가 개선됐다.
이러한 수치는 Google이 실제 CrUX 데이터를 분석한 결과다.
특정 프레임워크를 독립적으로 비교한 실험은 아니다.
CrUX API와 BigQuery 지원
새로운 지표는 다음 서비스에서 사용할 수 있다.
- CrUX BigQuery Dataset
- CrUX API
- CrUX History API
- CrUX Vis
CrUX Vis에서는 기존 Core Web Vitals와 새로운 실험 지표를 비교할 수 있다.
이를 통해 개발자는 자신의 웹사이트에서 Soft Navigation이 성능 측정 결과에 어느 정도 영향을 주는지 확인할 수 있다.
특히 SPA의 실제 사용자 성능 데이터를 분석하는 경우에는 초기 페이지 로딩과 클라이언트 측 페이지 전환을 구분해 보는 것이 중요하다.
다만 새로운 지표가 지원된다고 해서 모든 URL에서 반드시 데이터가 반환되는 것은 아니다.
기존 CrUX와 마찬가지로 데이터 수집 및 공개 기준을 충족해야 한다.
안정화 계획과 한계
이번 지표는 Experimental 단계다.
Google은 새로운 측정 방식에 대한 피드백을 수집하고 있으며, 측정 알고리즘이 변경될 수 있다고 설명한다.
2026년 11월에는 다음 실험 지표를 추가할 계획이라고 밝혔다.
- Navigation Type 관련 지표
- FCP(First Contentful Paint)
또한 2027년 5월에 실험 단계를 종료하는 것을 목표로 하고 있다.
다만 이는 확정된 정식 출시일이 아니다.
실험 결과와 커뮤니티 피드백에 따라 변경될 수 있다.
정식 지표로 전환되면 Search Console도 새로운 데이터를 사용하도록 변경할 계획이다.
이번 글은 React와 Vue 같은 SPA의 성능을 측정할 때 초기 페이지 로딩만으로 실제 사용자의 전체 경험을 설명하기 어렵다는 점을 구체적인 데이터로 보여준다.
특히 Core Web Vitals의 점수가 변경됐을 때 코드 성능의 실제 변화인지, 측정 방식의 변화인지 구분해야 한다는 점을 이해할 수 있는 자료다.
Tips and Tricks for Using TanStack Start with Cloudflare Durable Objects
- 저자: Adam Rackis / Master.dev
- 게시일: 2026-10-12
- 원문: https://blog.master.dev/tips-and-tricks-for-using-tanstack-start-with-cloudflare-durable-objects/
TanStack Start와 Cloudflare Durable Objects를 함께 사용하면서 발생하는 서버 함수의 문맥 전달, WebSocket 메시지 분리, TypeScript 반환 타입 문제를 실제 코드로 해결하는 과정을 설명한 글이다.
TanStack Start는 TanStack Router를 기반으로 하는 풀스택 React 프레임워크다.
서버에서 실행할 함수와 클라이언트에서 사용할 데이터를 하나의 TypeScript 코드베이스로 관리할 수 있다.
Cloudflare Durable Objects는 상태를 가진 실행 단위를 제공한다.
예를 들어 사용자별 세션이나 실시간 연결을 하나의 Durable Object에서 관리할 수 있다.
두 기술을 결합하면 서버 측 상태를 유지하면서 React 애플리케이션에 실시간 기능을 제공할 수 있다.
하지만 이러한 구조에서는 서버 함수와 Durable Object 사이의 경계를 어떻게 관리할지 결정해야 한다.
특히 Cloudflare의 Durable Object는 브라우저에서 직접 호출하는 일반적인 공개 API가 아니다.
Cloudflare 인프라 내부에서 접근하는 실행 단위다.
따라서 TanStack Start의 Server Function을 통해 접근하는 구조가 필요하다.
저자는 이러한 환경에서 발견한 세 가지 문제와 해결 방법을 설명한다.
Server Function마다 Durable Object를 조회하는 문제
첫 번째 문제는 반복적인 Durable Object 조회 코드다.
예를 들어 사용자의 AI 대화 세션을 관리하는 Durable Object가 있다고 가정해 보자.
서버 함수는 사용자의 인증 정보를 확인한 뒤 해당 사용자에게 대응하는 Durable Object를 가져와야 한다.
일반적으로 다음 과정이 필요하다.
- 요청에서 인증된 사용자 ID를 확인한다.
- Durable Object Namespace에 접근한다.
- 사용자 ID를 기준으로 Durable Object ID를 계산한다.
- 해당 Durable Object의 Stub을 가져온다.
- 실제 메서드를 호출한다.
문제는 여러 Server Function에서 동일한 작업을 반복해야 한다는 것이다.
예를 들어 다음 기능들이 같은 Durable Object를 사용한다고 가정해 보자.
- AI 세션 목록 조회
- 새 세션 생성
- 기존 세션 내용 조회
- 세션 삭제
- 프롬프트 실행 요청
모든 함수에 같은 조회 코드를 작성하면 이후 인증 방식이나 Durable Object 생성 규칙이 변경됐을 때 여러 파일을 수정해야 한다.
저자는 이 문제를 해결하기 위해 TanStack Start의 Middleware를 사용한다.
Middleware는 실제 Handler가 실행되기 전이나 후에 공통 작업을 처리하는 구성요소다.
예를 들어 사용자 인증과 로깅, 공통 데이터 조회 등에 활용할 수 있다.
Middleware에 Durable Object 추가
저자는 Durable Object를 가져오는 함수를 Middleware로 옮겼다.
Middleware는 필요한 Durable Object Stub을 조회한 뒤 Server Function의 Context에 추가한다.
이렇게 하면 실제 Server Function에서는 Durable Object를 직접 조회하는 과정이 필요하지 않다.
Handler는 Context에 포함된 Durable Object를 사용하면 된다.
이 구조의 장점은 공통적인 인프라 연결 코드를 실제 서비스 로직과 분리할 수 있다는 점이다.
하지만 첫 구현에서는 TypeScript 오류가 발생했다.
저자는 createMiddleware()를 이용해 Middleware를 생성했지만 기존 Global Middleware에서 추가한 인증 Context의 타입이 올바르게 전달되지 않았다.
이는 TanStack Start의 타입 추론 과정과 관련된 문제였다.
Global Middleware와 타입 추론 문제
TanStack Start에서는 Global Middleware를 이용해 모든 요청에 공통적인 문맥을 추가할 수 있다.
예를 들어 인증된 사용자 정보를 전역 Middleware에 추가할 수 있다.
하지만 이후 별도의 Middleware를 생성할 때 해당 Context가 TypeScript 타입에 반영되지 않을 수 있었다.
저자는 이를 해결하기 위해 기존에 생성한 startInstance에서 Middleware를 생성했다.
일반적인 createMiddleware()를 독립적으로 호출하는 대신 startInstance.createMiddleware()를 사용하는 방식이다.
이렇게 하면 해당 TanStack Start 인스턴스에 등록된 Middleware Context의 타입을 활용할 수 있다.
이후 Server Function은 해당 Middleware를 연결하고 context.wtDo를 통해 Durable Object에 접근한다.
이 방식에서는 인증 정보와 Durable Object 조회 로직이 Middleware에 집중된다.
다만 저자는 이러한 문제가 현재 TanStack의 타입 구현에 따른 제한일 수 있으며, 이후 라이브러리 버전에서 변경될 가능성이 있다고 설명한다.
따라서 이 해결 방법이 모든 향후 버전에서 반드시 필요한 구조라고 단정해서는 안 된다.
WebSocket 연결을 세션별로 분리
두 번째 문제는 WebSocket 메시지 전달이다.
Durable Objects는 WebSocket을 지원한다.
이를 이용하면 사용자의 연결을 유지하면서 서버에서 클라이언트로 실시간 메시지를 전달할 수 있다.
저자는 AI가 운동 계획을 생성하는 애플리케이션을 예로 든다.
사용자가 프롬프트를 제출하면 Durable Object가 새로운 생성 세션을 만들고, WebSocket으로 생성 진행 상황을 전달하는 구조다.
문제는 하나의 Durable Object가 여러 WebSocket 연결을 관리할 수 있다는 점이다.
예를 들어 사용자가 동시에 세 개의 AI 생성 세션을 열어 두었다고 가정해 보자.
세션 A에서 새로운 메시지가 발생했다고 해서 세션 B와 C에도 해당 메시지를 보낼 필요는 없다.
하지만 단순히 Durable Object의 모든 WebSocket을 조회하고 메시지를 전송하면 해당 Durable Object에 연결된 모든 클라이언트가 같은 메시지를 수신한다.
클라이언트가 자신과 관련 없는 메시지를 무시하도록 구현할 수도 있다.
그러나 저자는 서버에서 처음부터 해당 메시지가 필요한 연결만 선택하는 방식을 사용했다.
Tagged WebSocket
Cloudflare Durable Objects는 WebSocket 연결에 Tag를 부여할 수 있다.
저자는 각 WebSocket이 생성될 때 Session ID를 Tag로 연결했다.
예를 들어 Session ID가 123이라면 다음과 같은 Tag를 사용할 수 있다.
session:123
Durable Object가 WebSocket 연결을 수락할 때 acceptWebSocket()에 해당 Tag를 전달한다.
이후 특정 세션에 메시지를 보내야 할 때는 getWebSockets()에 같은 Tag를 전달한다.
이렇게 하면 해당 세션에 연결된 WebSocket만 조회할 수 있다.
따라서 세션별로 메시지를 분리하면서 모든 연결에 메시지를 방송할 필요가 없어진다.
하지만 Tag는 인증이나 접근 권한을 자동으로 보장하는 기능이 아니다.
사용자가 URL에서 Session ID를 변경할 수 있다면 다른 사용자의 세션에 연결을 시도할 가능성도 고려해야 한다.
따라서 실제 운영 환경에서는 WebSocket 연결을 수락하기 전에 해당 사용자가 요청한 세션에 접근할 권한이 있는지 확인해야 한다.
Tag는 메시지 전달 대상을 분류하는 수단이지 보안 경계를 대체하는 기능은 아니다.
Durable Object Stub의 반환 타입
세 번째 문제는 TypeScript 반환 타입이다.
Durable Object의 메서드에서 일반적인 객체 배열을 반환한다고 가정해 보자.
예를 들어 다음과 같은 데이터를 반환할 수 있다.
- 세션 ID
- 세션 이름
- 생성 시각
실제 메서드의 반환 타입은 단순한 객체 배열이다.
하지만 Durable Object Stub을 통해 메서드를 호출하면 TypeScript가 추론한 타입이 달라질 수 있다.
저자가 확인한 결과 반환 타입에 다음 속성이 추가됐다.
Disposable
또한 반환값은 Promise로 감싸졌다.
이는 Durable Object 메서드를 직접 호출하는 것이 아니라 Stub을 통해 접근하기 때문이다.
Stub은 Durable Object에 접근하는 과정과 원격 호출에 필요한 처리를 담당한다.
따라서 실제 메서드의 반환 타입과 Stub이 노출하는 타입 사이에 차이가 발생할 수 있다.
Disposable 타입과 직렬화 오류
단순한 객체 배열에서는 이러한 차이가 큰 문제가 되지 않을 수 있다.
TypeScript는 구조적 타입 시스템을 사용하기 때문이다.
필요한 속성이 존재한다면 추가 속성이 포함된 값을 더 단순한 타입으로 사용할 수 있다.
하지만 저자는 Union Type을 반환하는 함수에서 문제가 발생했다.
예를 들어 서버 함수가 다음 세 종류의 결과를 반환한다고 가정해 보자.
- 세션을 찾을 수 없음
- 오류 발생
- 세션 로딩 성공
이를 TypeScript의 Discriminated Union으로 표현할 수 있다.
Discriminated Union은 status처럼 공통된 구분 필드를 이용해 여러 객체 유형을 구분하는 방식이다.
이 구조에 Disposable 타입이 추가되자 TanStack Start의 직렬화 검증과 충돌했다.
특히 Symbol.dispose는 함수 형태의 속성을 포함한다.
하지만 서버에서 클라이언트로 전달하는 데이터는 직렬화 가능한 형태여야 한다.
함수를 일반적인 JSON 데이터처럼 전달할 수 없기 때문에 TypeScript 타입 검사에서 오류가 발생한 것이다.
Omit으로 Disposable을 제거하려던 시도
저자는 처음에 TypeScript의 Omit을 사용했다.
Omit은 객체 타입에서 특정 속성을 제외하는 유틸리티 타입이다.
이를 이용해 Symbol.dispose를 제거하려고 했다.
하지만 Union Type에 적용했을 때 기대한 형태의 타입이 생성되지 않았다.
일반적인 Omit은 Union의 각 구성요소에 자동으로 분배되지 않기 때문이다.
저자는 이후 Conditional Type을 사용해 Union의 각 구성요소에 Omit을 적용하는 방법도 시도했다.
이 방식으로 타입 오류를 해결할 수 있었다.
그러나 반환 타입이 매우 복잡해졌다.
실제 코드에서는 단순한 결과 객체를 반환하는데 TypeScript에서는 여러 Omit이 결합된 형태로 표현되는 문제가 남았다.
최종 해결: Server Function의 반환 타입 명시
저자가 최종적으로 선택한 방법은 더 단순했다.
Server Function의 반환 타입을 명시적으로 지정하는 것이다.
일반적으로 TypeScript에서는 함수의 반환 타입을 추론에 맡길 수 있다.
하지만 이번 경우에는 Durable Object Stub의 내부 구현에서 추가된 타입이 외부 Server Function까지 전달되는 것이 문제였다.
따라서 Server Function이 클라이언트에 반환할 타입을 명시적으로 선언했다.
예를 들어 Promise<SessionPayload>처럼 반환 타입을 지정하는 방식이다.
이렇게 하면 Durable Object의 내부 반환 타입이 Disposable을 포함하더라도 Server Function의 외부 계약은 개발자가 지정한 타입으로 유지할 수 있다.
이 방식은 인프라 내부에서 사용하는 타입과 애플리케이션이 외부에 제공하는 데이터 계약을 분리한다는 점에서도 의미가 있다.
단순히 TypeScript 오류를 없애는 것이 아니라 서로 다른 실행 환경 사이의 경계를 명확하게 만드는 선택이다.
제한사항과 설계상의 주의점
이 글에서 소개한 문제는 TanStack Start와 Cloudflare Durable Objects를 함께 사용하는 특정 구조에서 발생했다.
따라서 모든 React 프로젝트가 동일한 타입 문제를 겪는 것은 아니다.
또한 TanStack Start의 타입 추론과 Middleware 기능은 계속 변경될 수 있다.
향후 버전에서 일부 우회 방법이 더 이상 필요하지 않을 가능성도 있다.
저자가 제시한 WebSocket Tag와 Middleware 구성 역시 모든 애플리케이션에 적합한 유일한 구조는 아니다.
실제 요구사항에 따라 Durable Object를 사용자별, 조직별, 세션별로 구성할 수 있으며, 이에 따라 상태 공유 범위와 실행 비용이 달라진다.
그럼에도 이 글은 서버와 클라이언트 사이의 타입 경계, 실시간 연결의 메시지 분리, 공통 서버 문맥 관리가 실제 풀스택 React 애플리케이션에서 어떻게 연결되는지 구체적인 코드와 오류 사례로 보여준다.
특히 단순한 프레임워크 기능 소개가 아니라 실제 사용 과정에서 발견한 문제를 해결한 기록이라는 점에서 기술적 깊이가 있다.
How I stopped hassling my friends to test my apps, and just asked Grok Bot instead
- 저자: Lewis Cianci / LogRocket
- 게시일: 2026-10-14
- 원문: https://blog.logrocket.com/grok-bot-testing-friends/
개인 개발자가 운영하는 웹서비스에서 여러 AI 에이전트에게 사용자 역할을 부여하고, 실제 브라우저에서 서비스를 테스트한 뒤 코드 수정과 배포 검증까지 연결한 사례를 설명한 글이다.
일반적인 웹서비스 개발에서는 기능을 구현하는 것만큼 실제 사용자가 해당 기능을 올바르게 사용할 수 있는지 확인하는 과정도 중요하다.
예를 들어 회원가입과 게시글 작성 기능이 정상적으로 작동한다고 해도 사용자가 실제로 해당 기능을 쉽게 찾을 수 있는지는 별개의 문제다.
버튼의 이름이 모호하거나 입력 양식이 너무 복잡하면 기술적으로는 정상적인 기능도 실제로 사용하기 어려울 수 있다.
저자는 이러한 문제를 해결하기 위해 여러 AI 에이전트를 QA 과정에 도입했다.
실험 대상은 저자가 직접 개발한 CheapMulch라는 웹서비스다.
테스트 대상 서비스의 구조
CheapMulch는 호주의 나무 관리 업체와 무료 멀치를 원하는 사람을 연결하는 서비스다.
나무 관리 업체는 작업 과정에서 발생한 멀치를 처리해야 한다.
반면 정원이나 화단에 사용할 멀치를 원하는 사람은 이를 구하고 싶어 한다.
서비스는 두 사용자가 서로 연결될 수 있도록 지도와 등록 기능을 제공한다.
기술 구성은 다음과 같다.
| 영역 | 사용 기술 |
|---|---|
| 프론트엔드 | Angular 22, Angular Material |
| 백엔드 | .NET 10 |
| 데이터베이스 | PostgreSQL |
| 지도 | Leaflet, OpenStreetMap |
| 실시간 통신 | SignalR |
| 인증 | Firebase Authentication |
| 결제 | Stripe |
| 배포 | Docker, Dokploy |
이처럼 하나의 서비스에는 여러 기술이 결합돼 있다.
따라서 테스트해야 할 내용도 단순한 화면 렌더링에 그치지 않는다.
실제 사용자는 회원가입을 하고, 지도에서 위치를 선택하고, 정보를 등록하고, 다른 사용자와 메시지를 교환해야 한다.
저자는 기존에도 일부 브라우저 자동화 테스트를 사용하고 있었다.
하지만 이러한 테스트는 개발자가 미리 정의한 동작만 확인했다.
예를 들어 버튼을 클릭했을 때 올바른 페이지로 이동하는지는 확인할 수 있지만, 버튼의 문구 자체가 사용자에게 이해하기 어려운지는 판단하기 어렵다.
저자는 이러한 문제를 찾기 위해 실제 사용자의 입장에서 서비스를 탐색하는 AI 에이전트를 구성했다.
서로 다른 사용자 역할을 가진 두 개의 에이전트
저자는 처음에 두 개의 Grok Bot 에이전트를 사용했다.
첫 번째 에이전트는 멀치를 처리하려는 나무 관리 업체의 역할을 맡았다.
두 번째 에이전트는 무료 멀치를 요청하는 일반 사용자의 역할을 맡았다.
두 에이전트에는 서비스의 목적과 각자의 역할을 설명했다.
또한 새로운 계정을 생성해도 되며, 서비스를 탐색하면서 사용성 문제를 찾아 보고서로 정리하도록 요청했다.
흥미로운 점은 각 에이전트에게 모든 테스트 단계를 상세하게 지정하지 않았다는 사실이다.
에이전트들은 서로 작업 범위를 나눴다.
첫 번째 에이전트는 나무 관리 업체의 서비스 이용 과정을 확인했다.
두 번째 에이전트는 멀치를 요청하는 사용자의 흐름을 확인했다.
이후 실제 브라우저에서 회원가입과 입력 양식 작성, 페이지 탐색 등을 수행했다.
발견한 사용성 문제
에이전트들은 여러 구체적인 문제를 발견했다.
예를 들어 멀치를 등록하려는 사용자가 버튼을 클릭하면 앞으로 어떤 내용을 입력해야 하는지 설명하는 화면 없이 로그인 페이지로 이동했다.
또한 비슷한 이름의 버튼이 여러 개 존재했다.
사용자는 각각의 버튼이 어떤 기능을 수행하는지 구분하기 어려웠다.
지도에는 여러 유형의 위치 정보가 표시됐지만 이를 설명하는 범례가 없었다.
이러한 문제는 단순한 기능 테스트만으로는 발견하기 어려울 수 있다.
에이전트들은 다음과 같은 실제 동작 오류도 발견했다.
- 주소 자동완성 기능이 작동하지 않음
- 지도 Marker가 잘못된 위치에 고정됨
- 게시 버튼이 활성화되지 않음
- 날짜가 하루 차이로 표시됨
- 서로 다른 화면에서 가격 안내가 일치하지 않음
- 일부 입력 양식에 지나치게 많은 필수 항목 존재
날짜가 하루 차이로 표시되는 문제는 시간대 처리와 관련돼 있었다.
호주처럼 여러 시간대를 사용하는 지역에서는 날짜와 시간을 단순한 문자열로 처리하거나 UTC와 로컬 시간을 혼동하면 이러한 오류가 발생할 수 있다.
이 사례는 AI 에이전트가 단순히 버튼의 동작을 확인하는 수준을 넘어 실제 사용자 흐름을 따라가며 문제를 발견할 수 있다는 점을 보여준다.
하지만 여기서 발견한 문제들이 모두 자동으로 정확하게 판단된 것은 아니다.
사용성에 관한 일부 판단은 주관적일 수 있으며, 실제 사용자 조사를 완전히 대체하지는 못한다.
세 번째 에이전트: 코드 수정 담당
저자는 발견한 문제를 해결하기 위해 세 번째 에이전트를 추가했다.
이 에이전트는 코드 수정 역할을 맡았다.
첫 번째와 두 번째 에이전트가 작성한 보고서를 읽고 실제 소스코드를 변경하는 방식이다.
수정이 완료되면 다시 테스트 에이전트에게 확인을 요청했다.
이렇게 하면 다음과 같은 작업 흐름이 만들어진다.
- 사용자 역할의 에이전트가 문제를 발견한다.
- 문제를 보고서로 정리한다.
- 코드 수정 에이전트가 보고서를 확인한다.
- 관련 코드를 수정하고 Push한다.
- 테스트 에이전트가 변경된 서비스를 다시 확인한다.
하지만 이 과정에서 새로운 문제가 발생했다.
코드를 Push했다고 해서 실제 운영 서비스에 해당 코드가 배포됐다는 보장은 없다는 것이다.
배포된 Commit을 검증하는 방법
저자의 서비스는 Dokploy를 이용해 배포된다.
새로운 Commit이 Push되면 Docker 이미지를 빌드하고 서비스에 배포한다.
하지만 빌드에는 시간이 걸릴 수 있다.
또한 빌드가 실패하면 기존 버전의 서비스가 계속 실행될 수 있다.
이때 테스트 에이전트가 너무 빨리 서비스를 확인하면 아직 배포되지 않은 이전 버전을 테스트할 수 있다.
저자는 이 문제를 해결하기 위해 현재 배포된 Commit SHA를 HTTP 응답 헤더에 포함하도록 했다.
헤더 이름은 다음과 같다.
X-Commit-Sha
코드를 수정한 에이전트는 자신이 Push한 Commit SHA를 알려준다.
테스트 에이전트는 실제 웹서비스의 응답 헤더를 확인한다.
두 값이 일치한다면 해당 Commit이 적용된 서비스를 테스트하고 있다고 판단할 수 있다.
이러한 방식은 사람이 아닌 자동화된 에이전트가 테스트를 수행할 때 유용하다.
배포가 완료됐는지 추측하지 않고 구체적인 식별자로 확인할 수 있기 때문이다.
다만 HTTP 응답 헤더가 올바른 Commit을 나타내도록 실제 빌드와 배포 과정에서 해당 값이 정확하게 설정돼야 한다.
잘못된 환경 변수나 캐시된 응답을 사용한다면 실제 실행 버전과 다른 정보가 표시될 수 있다.
네 번째 에이전트: 배포 실패 분석
Commit SHA 헤더를 추가하는 과정에서 실제로 문제가 발생했다.
코드 수정 에이전트가 CORS 설정을 변경하면서 괄호 하나를 누락한 것이다.
그 결과 .NET 빌드가 실패했다.
Dokploy는 마지막으로 정상 배포된 컨테이너를 계속 실행했다.
따라서 웹서비스 자체는 정상적으로 응답했다.
하지만 새로운 Commit은 배포되지 않았다.
테스트 에이전트는 HTTP 응답 헤더에서 새로운 Commit SHA를 확인할 수 없었다.
저자는 이를 해결하기 위해 네 번째 에이전트를 추가했다.
네 번째 에이전트는 Dokploy의 배포 상태와 로그를 확인하는 역할을 맡았다.
실제 빌드 로그에는 다음 오류가 기록돼 있었다.
CS1026: ) expected
이는 C# 코드에서 닫는 괄호가 누락됐다는 오류다.
배포 확인 에이전트는 해당 오류를 발견하고 코드 수정 에이전트에게 전달했다.
코드 수정 에이전트는 괄호를 수정한 뒤 새로운 Commit을 Push했다.
배포 확인 에이전트는 빌드가 정상적으로 완료됐는지 확인했다.
이후 테스트 에이전트들이 다시 서비스를 검사했다.
이 과정은 테스트, 코드 수정, 배포 확인이라는 서로 다른 역할을 가진 에이전트들이 정보를 교환하며 하나의 작업을 처리하는 구조를 보여준다.
실제로 놓친 문제
저자는 이러한 자동화가 모든 문제를 발견한 것은 아니라고 설명한다.
특히 입력 양식에서 텍스트가 서로 겹치는 UI 오류를 에이전트들이 발견하지 못했다.
해당 화면은 이미 여러 번 테스트한 상태였다.
하지만 실제로 사람이 화면을 확인했을 때 설명 문구가 다른 입력 필드의 Label과 겹쳐 있었다.
저자가 이 문제를 직접 발견하고 스크린샷을 전달한 이후에야 에이전트가 유사한 문제를 추가로 찾고 수정했다.
따라서 저자는 AI 에이전트가 사용성 문제를 탐색하는 보조 수단으로는 유용하지만 모든 시각적 오류를 자동으로 발견한다고 기대해서는 안 된다고 설명한다.
특히 UI의 복잡한 레이아웃이나 미묘한 시각적 문제는 사람이 직접 확인해야 할 수 있다.
자동화 테스트와의 관계
저자는 AI 에이전트를 기존 E2E 테스트의 완전한 대체 수단으로 사용하지 않았다.
E2E(End-to-End) 테스트는 사용자의 실제 작업 흐름을 시작부터 끝까지 검증하는 테스트다.
예를 들어 로그인부터 주문 완료까지의 과정을 자동으로 실행할 수 있다.
이러한 테스트는 특정 기능이 변경되지 않았는지 반복적으로 확인하는 데 적합하다.
반면 이번 AI 에이전트 기반 테스트는 개발자가 사전에 정의하지 않은 사용성 문제를 탐색하는 데 초점을 맞췄다.
두 방식은 서로 다른 장점을 가진다.
E2E 테스트는 재현 가능한 동작 검증에 적합하다.
AI 에이전트는 예측하지 못한 사용자 흐름과 모호한 UI 문제를 탐색하는 데 활용할 수 있다.
따라서 실제 서비스에서는 두 접근을 함께 사용할 수 있다.
보안과 운영상의 제한
이 실험에서는 여러 에이전트가 하나의 클라우드 컴퓨터를 공유했다.
해당 환경에는 브라우저 세션과 파일, 로그인 정보가 포함될 수 있었다.
또한 일부 에이전트에는 소스코드를 수정하고 배포를 시작할 수 있는 권한이 있었다.
이러한 구조에서는 Prompt Injection과 과도한 권한 부여 문제가 발생할 수 있다.
예를 들어 에이전트가 웹페이지에 포함된 악의적인 지시를 신뢰하고 실행한다면 의도하지 않은 파일 수정이나 외부 요청이 발생할 수 있다.
따라서 운영 환경에서는 에이전트별 접근 권한을 제한하고, 중요한 변경에는 별도의 승인 과정을 두는 것이 필요하다.
저자는 개인 프로젝트에서는 이러한 위험을 감수했지만 더 중요한 서비스에서는 별도의 보안 설계가 필요하다고 설명한다.
실험 결과의 한계
이번 글은 하나의 개인 프로젝트에서 수행한 경험을 정리한 것이다.
기존 QA 방식과 AI 에이전트 방식의 오류 탐지율을 통계적으로 비교한 Benchmark는 제공하지 않는다.
또한 실제 사용자를 대상으로 한 UX 조사와 동일한 결과를 얻었다고 주장하지 않는다.
따라서 AI 에이전트가 모든 프로젝트에서 사람보다 더 정확하게 문제를 발견한다고 결론 내릴 수는 없다.
그럼에도 이 사례는 개발된 코드가 실제로 배포됐는지 확인하고, 배포 실패와 사용자 흐름의 오류를 서로 다른 에이전트가 검증하도록 구성하는 방법을 보여준다.
특히 HTTP 응답에 Commit SHA를 포함해 현재 실행 중인 코드를 검증하는 설계는 AI 에이전트 여부와 관계없이 자동화된 배포 및 테스트 시스템에서 참고할 수 있는 접근이다.