웹 개발 데일리 브리핑 — 2026-10-08
Soshy·

기준 시점: 2026-10-08 06:00 KST
조사 범위: 2026-10-07 06:00 ~ 2026-10-08 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
Vercel Flags, timestamp 기반 타기팅 지원 — 날짜와 시간을 이용한 기능 공개·프로모션 제어
- 발표일: 2026-10-07 — 정확한 게시 시각 미확인
- 원문: https://vercel.com/changelog/timestamp-attributes-are-now-supported-in-vercel-flags
Vercel이 Flags에 timestamp 타입의 entity attribute를 추가했다. 기존 feature flag가 사용자 ID, 국가, 구독 등급, 조직과 같은 속성을 기준으로 기능을 활성화하는 데 주로 사용됐다면, 이제 날짜와 시간을 조건으로 삼아 특정 기능이나 UI를 노출할 수 있다.
예를 들어 프로모션 페이지를 특정 기간에만 공개하거나, 신규 가입자에게 가입 후 일정 시간이 지난 시점부터 기능을 제공하거나, 정해진 시각 이후 새로운 UI를 활성화하는 정책을 Flags에서 정의할 수 있다.
기존에도 애플리케이션 코드에서 현재 시간을 확인하거나 배포·스케줄러를 조합하면 같은 결과를 만들 수 있었다. 그러나 이런 방식은 시간 조건과 기능 노출 정책이 애플리케이션 코드에 분산된다는 문제가 있다. 이번 변경은 시간 조건을 feature flag의 평가 규칙으로 옮기는 것에 가깝다.
Timestamp attribute는 Vercel Dashboard 또는 CLI에서 정의할 수 있다. 예를 들어 system이라는 entity에 time 속성을 추가하고 타입을 timestamp로 지정하는 구조다.
실제 flag를 평가할 때는 애플리케이션에서 해당 attribute의 값을 제공해야 한다. 값의 형식은 밀리초 단위 Unix epoch timestamp이며 JavaScript의 Date.now() 반환값을 그대로 사용할 수 있다.
공식 예제에서는 flags/next의 dedupe()를 사용한다. 동일한 요청 안에서 여러 flag를 평가할 때 매번 Date.now()를 새로 호출하면 몇 밀리초의 차이 때문에 서로 다른 판단이 나올 수 있다. 특히 프로모션 시작이나 종료 시각처럼 경계에 걸친 요청에서는 하나의 페이지 안에서 일부 기능만 활성화되는 문제가 발생할 수 있다.
dedupe()로 현재 시간을 계산하는 identify 함수를 묶으면 같은 요청에서 평가하는 flag들이 동일한 timestamp를 공유한다. 이는 단순한 캐싱 최적화가 아니라 요청 단위의 평가 일관성을 유지하기 위한 처리다.
Targeting rule에는 다음 네 가지 시간 비교 연산자가 추가됐다.
after: 지정한 시각 이후at-or-after: 지정한 시각 이상before: 지정한 시각 이전at-or-before: 지정한 시각 이하
여러 조건을 조합하면 시작 시각은 포함하고 종료 시각은 제외하는 방식으로 기간을 표현할 수 있다. Dashboard에서는 사용자의 로컬 시간대를 기준으로 날짜를 선택할 수 있으며 CLI에서는 ISO 8601 형식의 timestamp를 사용한다.
이 기능은 자동으로 정해진 시각에 서버 작업을 실행하는 스케줄러와는 다르다. 애플리케이션이 flag를 평가하는 시점에 제공된 timestamp를 조건과 비교하는 방식이다. 따라서 이미 브라우저에 표시된 페이지가 시간이 지났다는 이유만으로 자동으로 바뀌는 것은 아니다. 변경된 결과를 화면에 반영하려면 애플리케이션에서 flag를 다시 평가하는 흐름이 필요하다.
이번 기능은 Vercel Flags를 사용하는 애플리케이션에 적용되며, Next.js 전용 API 변경이나 React 렌더링 모델의 변경은 아니다. 시간에 따라 달라지는 UI 노출 정책을 애플리케이션 구현과 분리해 관리할 수 있도록 한 업데이트다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
3️⃣ 백엔드·인프라
Netlify AI Gateway, OpenAI Decisions API 지원 — 자유 형식 텍스트 생성 대신 제한된 선택지와 확률을 반환하는 서버리스 API
- 발표일: 2026-10-07 — 정확한 게시 시각 미확인
- 안정화 단계: Decisions API Beta
- 원문: https://www.netlify.com/changelog/openai-decisions-api-ai-gateway/
Netlify가 AI Gateway에서 OpenAI Decisions API를 사용할 수 있도록 지원 범위를 확대했다.
기존의 일반적인 LLM integration은 자연어 입력을 보내고 모델이 생성한 문자열이나 JSON을 받아 애플리케이션에서 해석하는 구조가 많았다. 반면 Decisions API는 개발자가 미리 정의한 질문과 선택지를 전달하고, 모델이 그중 하나를 선택하도록 설계된 API다.
예를 들어 전자상거래 서비스에서 고객의 반품 요청을 처리한다고 가정해 보자.
고객은 반품 사유를 자유롭게 입력할 수 있다. 어떤 사용자는 상품 크기가 맞지 않는다고 작성하고, 다른 사용자는 지퍼가 고장 났다고 설명하며, 또 다른 사용자는 단순 변심을 표현할 수 있다.
이 입력을 일반적인 텍스트 생성 모델에 전달하면 응답 문구가 매번 달라질 수 있다. 애플리케이션은 그 결과를 정규화하거나 별도의 JSON schema로 검증해야 한다.
Decisions API에서는 개발자가 먼저 wrong_size, defective, not_as_described, changed_mind 같은 허용된 선택지를 정의한다.
모델은 사용자의 입력을 분석하고 다음과 같은 구조화된 결과를 반환한다.
choice: 정의된 선택지 가운데 최종 선택probabilities: 각 선택지에 할당한 확률confidence: 해당 판단에 대한 신뢰도
따라서 애플리케이션은 결과 문자열을 다시 해석하지 않고 choice 값을 기준으로 후속 처리를 분기할 수 있다.
Netlify의 공식 예제에서는 고객이 지퍼가 고장 났다고 설명한 반품 요청에 대해 defective가 선택된다. 각 선택지의 확률도 함께 제공되므로 분류가 불확실한 요청을 자동 처리하지 않고 별도의 검토 단계로 보낼 수 있다.
중요한 차이는 출력 형식이 제한된다는 것이지 모델의 판단이 반드시 정확하다는 의미는 아니라는 점이다. 반환값이 사전에 정의한 선택지 가운데 하나라고 해도 실제 반품 사유를 잘못 분류할 가능성은 남아 있다.
결제 취소, 환불 승인, 계정 제한처럼 사용자에게 직접적인 영향을 주는 작업에서는 선택지의 형식이 올바르다는 사실과 판단의 정확성을 구분해야 한다.
Netlify에서는 기존 OpenAI SDK를 사용해 Netlify Functions 안에서 Decisions API를 호출할 수 있다. AI Gateway가 provider 연결을 처리하기 때문에 별도의 API key를 직접 관리하지 않는 구성을 지원한다.
공식 예제는 HTTP POST 요청으로 전달된 반품 사유를 읽고 openai.decisions.create()를 호출한 뒤 첫 번째 답변을 JSON 응답으로 반환하는 서버리스 함수다.
현재 Decisions API는 Beta 단계이며 Netlify의 발표 기준으로 gpt-6-luna 모델을 사용한다. 일반적인 AI Gateway의 모든 model이 Decisions API를 지원한다는 뜻은 아니다.
이번 변경은 새로운 서버리스 실행 환경을 추가한 것이 아니라 기존 Netlify Functions에서 사용할 수 있는 구조화된 AI 판단 API의 지원 범위를 확대한 것이다.
4️⃣ 개발 도구 및 보안
GitHub Copilot 로컬 샌드박싱 GA — 코딩 에이전트의 파일·네트워크·인증 정보 접근을 운영체제 수준에서 제한
- 발표일: 2026-10-07 — 정확한 게시 시각 미확인
- 안정화 단계: Generally Available
- 원문: https://github.blog/changelog/2026-10-07-local-sandboxing-for-github-copilot-now-generally-available/
GitHub가 Copilot의 로컬 샌드박싱 기능을 정식 출시했다.
적용 대상은 GitHub Copilot CLI, GitHub Copilot app, 그리고 Agent Host를 사용하는 VS Code session이다.
코딩 에이전트는 기존의 코드 자동완성 도구와 달리 파일을 읽고 수정하며, terminal command를 실행하고, package를 설치하거나 외부 서비스에 요청을 보내는 작업까지 수행한다.
이 과정에서 에이전트에 전달된 지시와 실제 운영체제가 허용하는 권한 사이의 차이가 중요한 보안 문제가 된다.
예를 들어 에이전트에게 특정 repository의 bug를 수정하라고 요청했더라도, 실행 중인 shell이 사용자의 home directory 전체를 읽을 수 있다면 repository 밖에 있는 SSH key나 cloud credential에도 접근할 가능성이 생긴다.
마찬가지로 코드를 분석하기 위해 실행한 command가 외부 네트워크에 자유롭게 접속할 수 있다면 의도하지 않은 데이터 전송이 발생할 수 있다.
이번 기능은 이런 위험을 줄이기 위해 에이전트가 실행하는 도구와 command에 별도의 실행 권한 경계를 적용한다.
구현에는 Microsoft의 오픈소스 프로젝트인 Microsoft eXecution Container(MXC) 가 사용된다.
MXC는 공통된 sandbox policy를 Windows, macOS, Linux에서 지원하는 운영체제별 native control로 변환한다. 따라서 플랫폼마다 완전히 다른 정책 언어를 사용하지 않고도 파일시스템과 네트워크 접근 범위를 정의할 수 있다.
샌드박스에서는 다음과 같은 권한을 제어할 수 있다.
- 에이전트가 읽거나 수정할 수 있는 파일과 디렉터리
- 인터넷과 로컬 네트워크 접근
- Git credential과 GitHub CLI credential 접근
- 지원되는 로컬 MCP server와 language server 사용
- 조직에서 강제하는 보안 정책
특히 enterprise 환경에서는 조직 관리자가 샌드박싱 사용을 의무화하고, 개별 개발자가 해당 정책을 임의로 완화하지 못하도록 설정할 수 있다.
기존에는 coding agent에게 “특정 디렉터리 밖의 파일을 읽지 말라”는 식의 지시를 전달하는 방식에 의존하기 쉬웠다. 그러나 자연어 지시는 모델의 행동을 유도할 뿐 운영체제의 실제 접근 권한을 변경하지 않는다.
반면 이번 기능은 모델의 지시 해석과 도구의 실행 권한을 분리한다.
GitHub는 어떤 모델을 사용하는지와 샌드박스가 어떤 권한을 허용하는지는 별개의 문제라고 명시했다. 클라우드 모델을 사용하든 다른 모델을 사용하든 도구 실행에는 동일한 sandbox policy를 적용할 수 있다.
샌드박싱이 적용됐다고 해서 모델의 응답이 정확해지거나 생성된 코드가 자동으로 안전해지는 것은 아니다. 또한 허용된 권한 안에서 실행되는 명령은 여전히 잘못된 변경을 만들 수 있다.
이 기능은 모델이 생성하는 결과의 품질을 보장하기보다 에이전트가 실수하거나 부적절한 명령을 실행하더라도 접근 가능한 자원의 범위를 제한하는 보안 장치다.
로컬 샌드박싱은 GitHub Copilot에 추가 비용 없이 포함된다.
GitHub Secret Protection, 전용 AI 모델 도입 — 정형화되지 않은 비밀번호까지 코드 문맥으로 탐지
- 발표일: 2026-10-07 — 정확한 게시 시각 미확인
- 기존 AI 탐지 알림: 신규 모델로 자동 전환
- AI Push Protection: Private Preview
- Copilot
/security-review통합: 향후 Private Preview 예정 - 원문: https://github.blog/changelog/2026-10-07-purpose-built-model-for-leaked-secret-detection/
GitHub가 소스코드에 포함된 credential을 탐지하기 위한 전용 AI 모델을 공개하고 Secret Protection의 탐지 기능을 확대했다.
기존 secret scanning은 특정 provider의 token 형식이나 정규식으로 식별할 수 있는 credential을 탐지하는 데 강점이 있다.
예를 들어 일정한 prefix나 길이를 가진 API key는 코드에서 비교적 명확하게 찾아낼 수 있다. 그러나 개발자가 직접 만든 password나 내부 시스템에서 사용하는 credential은 정해진 형식이 없는 경우가 많다.
이런 값은 문자열 자체만 보면 일반적인 테스트 데이터, 설정값 또는 상수와 구분하기 어렵다.
새 모델은 문자열 주변의 코드 문맥까지 분석해 해당 값이 실제 credential일 가능성을 판단한다.
GitHub는 이 모델이 일반적인 문장이나 코드를 생성하는 LLM이 아니라 secret detection을 위해 fine-tuning한 전용 모델이라고 설명한다.
이번 발표에는 세 가지 서로 다른 적용 단계가 포함된다.
첫 번째는 이미 제공 중인 AI-detected secret alerts다.
기존에 AI-detected Password alerts를 사용하는 고객은 별도의 설정 변경 없이 새 모델로 전환된다. 해당 탐지 기능은 GitHub Secret Protection(GHSP) 또는 GitHub Advanced Security(GHAS)에 포함되며 추가 AI Credit 과금 없이 제공된다.
두 번째는 AI Push Protection이다.
기존의 push protection이 알려진 형식의 secret을 Git push 시점에 차단하는 데 집중했다면, 새 기능은 정형화되지 않은 credential도 push 과정에서 탐지하는 것을 목표로 한다.
이 기능은 현재 Private Preview이며 GitHub Team 또는 GitHub Enterprise Cloud에서 GHSP·GHAS를 구매한 조직을 대상으로 제공될 예정이다.
관리자가 직접 활성화해야 하며 조직이나 enterprise의 기존 policy를 따라야 한다.
GitHub는 향후 이 기능의 사용량을 AI Credits로 과금할 계획이다. 실제로 push를 차단하지 않았더라도 검사가 수행됐다면 credit이 소비될 수 있다.
따라서 기존 secret scanning 알림이 추가 비용 없이 제공된다는 점과 AI Push Protection 검사가 앞으로 별도로 과금될 수 있다는 점을 구분해야 한다.
세 번째는 GitHub Copilot의 /security-review 통합이다.
현재 /security-review는 변경된 코드에서 취약점을 찾아 검토 결과와 수정 제안을 제공하는 기능이다.
GitHub는 향후 여기에 전용 secret classifier를 추가해 일반적인 LLM 기반 코드 검토와 별도로 credential 노출 여부를 검사할 계획이다.
다만 새 classifier를 이용한 검사는 아직 출시된 기능이 아니다. Private Preview가 향후 제공될 예정이며 기본값은 비활성화다.
/security-review 명령을 실행한다고 자동으로 새로운 유료 secret 검사가 켜지는 것도 아니다. 사용자는 해당 기능을 별도로 활성화해야 한다.
GitHub Enterprise Server에서는 향후 GHES 3.23에 AI-detected alerts를 Public Preview로 제공할 계획이다. 그러나 같은 서버 버전에 AI Push Protection이나 Copilot의 새로운 security-review 검사가 함께 제공되는 것은 아니다.
이번 발표는 코드 작성 후 repository를 검사하는 방식에서 코드 작성·검토·push 과정까지 credential 탐지를 앞당기려는 확장으로 볼 수 있다.
GitHub Copilot CLI, Ollama 로컬 모델 자동 탐색 지원 — 세션을 재시작하지 않고 모델 전환
- 발표일: 2026-10-07 — 정확한 게시 시각 미확인
- 적용 버전: GitHub Copilot CLI 1.0.94-0 이상
- 원문: https://github.blog/changelog/2026-10-07-discover-local-models-in-github-copilot-cli/
GitHub Copilot CLI가 로컬에서 실행 중인 Ollama 모델을 직접 탐색하고 선택하는 기능을 추가했다.
기존 Copilot CLI에서는 GitHub가 제공하는 클라우드 모델이나 사용자가 수동으로 설정한 provider를 중심으로 모델을 선택했다.
이번 업데이트부터는 /model 명령을 실행하면 현재 설정된 모델과 GitHub Copilot의 cloud model뿐 아니라 실행 중인 로컬 Ollama instance에서 사용할 수 있는 모델도 표시된다.
모델을 선택하면 provider와 endpoint를 확인한 뒤 현재 세션에서 바로 사용하거나, 모델 목록에만 추가할 수 있다.
기존 CLI session을 종료하고 다시 시작할 필요는 없다.
다만 이 기능은 Ollama와 모델을 자동으로 설치하지 않는다. 개발자가 먼저 Ollama를 설치하고 필요한 모델을 내려받아 실행하고 있어야 한다.
또한 Ollama에서 실행 가능한 모든 모델이 지원되는 것은 아니다. Copilot의 agent workflow가 정상적으로 동작하려면 해당 모델이 tool calling과 streaming을 모두 지원해야 한다.
Tool calling은 모델이 단순히 코드를 설명하는 데서 끝나지 않고 command 실행이나 파일 조회 같은 도구 호출을 구조화된 형태로 요청하는 기능이다. Streaming은 모델이 전체 응답을 완성할 때까지 기다리지 않고 결과를 순차적으로 전달하는 방식이다.
이 두 기능이 지원되지 않으면 일반적인 채팅 모델로는 사용할 수 있어도 Copilot CLI의 agent workflow에는 적합하지 않을 수 있다.
이번 발표에서 GitHub는 로컬 모델 선택과 오프라인 실행을 명확하게 구분했다.
로컬 모델을 선택한다고 해서 Copilot CLI의 다른 네트워크 통신이나 telemetry가 자동으로 중단되는 것은 아니다.
오프라인 모드는 별도로 COPILOT_OFFLINE=true를 설정해야 한다. 반대로 오프라인 모드를 설정했더라도 remote provider를 직접 선택했다면 해당 provider와의 통신에서 prompt나 code context가 네트워크로 전송될 수 있다.
따라서 모델의 inference가 어디에서 실행되는지와 Copilot CLI 전체 workflow가 어떤 데이터를 외부로 전송하는지는 별개의 문제다.
이번 기능은 로컬 모델을 직접 사용하는 개발자의 설정 비용을 줄이는 업데이트이며, GitHub는 별도로 로컬 모델을 활용한 intelligent routing도 발표했지만 해당 기능의 일반 제공 시점은 아직 확정하지 않았다.
📚 추천 글
Making React Context Cheap with React Compiler
- 저자: jjenzz
- 게시일: 2026-10-04
- 원문: https://jjenzz.com/making-react-context-cheap/
React Context를 사용하는 애플리케이션에서 자주 등장하는 “Context 값 하나가 바뀌면 모든 consumer가 다시 렌더링된다”는 성능 문제를 실제 benchmark로 검증한 글이다.
예제로 사용한 것은 5,000개의 radio button이 하나의 선택 상태를 공유하는 UI다.
일반적인 Context 구조에서는 선택된 radio가 변경될 때 Provider의 value가 달라지고, 해당 Context를 읽는 radio component 5,000개가 모두 다시 렌더링된다.
실제로 선택 상태가 바뀌는 radio는 이전에 선택돼 있던 것과 새로 선택된 것 두 개뿐인데, 나머지 4,998개 component도 Context 변경의 영향을 받는다.
이 문제를 해결하기 위해 React 생태계에서는 Context Selector API가 오랫동안 논의됐다. Context 전체를 구독하는 대신 자신이 필요한 값만 선택하고, 선택된 값이 바뀌지 않았다면 렌더링하지 않는 방식이다.
그러나 React에는 아직 공식 useContextSelector API가 없다.
글의 저자는 이런 상황에서 React 외부 store로 상태를 옮기는 것이 정말 필요한지 의문을 제기한다.
비교 대상은 세 가지다.
첫 번째는 useSyncExternalStore를 이용하는 store 방식이다. 각 radio가 자신에게 필요한 checked 상태만 구독하기 때문에 실제 선택 상태가 달라진 두 radio만 다시 렌더링된다.
두 번째는 React가 공식 문서에서 권장하는 Context consumer와 memoized view를 분리하는 구조다.
바깥 component는 Context를 읽고 checked 값을 계산하지만, 실제 input을 렌더링하는 안쪽 component는 React.memo로 감싼다.
Context가 변경되면 바깥 component 5,000개는 다시 실행된다. 그러나 안쪽 view는 전달받은 checked 값이 바뀌지 않았으면 다시 렌더링하지 않는다.
세 번째는 React Compiler를 사용하는 방식이다.
별도의 memoized child component 없이 Context를 읽고 JSX를 반환하는 구조를 유지한다. React Compiler는 component 내부에서 사용되는 값과 JSX를 분석하고 이전 렌더링 결과를 재사용할 수 있는 부분을 자동으로 memoization한다.
따라서 5,000개의 Context consumer가 다시 실행되더라도 실제로 변경되지 않은 JSX를 새로 만들거나 처리하는 작업을 줄일 수 있다.
저자는 이 세 방식을 production build에서 비교했다.
테스트 환경은 MacBook Pro M3 Pro, 36GB RAM, Chrome이며 5,000개의 radio button을 사용했다. 수치는 p95이고 Chrome DevTools의 CPU 4배 slowdown 조건도 포함한다.
선택 변경 시 소요 시간은 다음과 같았다.
| 방식 | 일반 CPU | CPU 4배 slowdown |
|---|---|---|
useSyncExternalStore | 19.2ms | 64.9ms |
Context + React.memo | 18.9ms | 72.6ms |
| Context + React Compiler | 18.4ms | 63.6ms |
흥미로운 점은 실제 다시 렌더링된 component의 개수가 크게 달라도 사용자 관점에서 측정한 처리 시간이 거의 비슷했다는 것이다.
Store 방식은 두 consumer만 업데이트했지만, Context 기반 구현은 모든 consumer가 다시 실행됐음에도 대부분의 렌더링 작업이 생략돼 비슷한 결과를 보였다.
React Compiler를 적용한 버전은 별도의 selector나 child component 없이도 경쟁력 있는 결과를 보였다.
물론 이 benchmark는 5,000개의 단순 radio button을 대상으로 한 제한된 실험이다. 저자도 이를 근거로 모든 external store보다 Context가 빠르다고 일반화하지 않는다.
React Compiler가 비싼 계산을 항상 제거하는 것도 아니며, 복잡한 데이터 처리나 React 외부 상태와 연동하는 환경에서는 다른 결과가 나올 수 있다.
External store를 사용할 때는 React concurrent rendering과의 호환성도 고려해야 한다. 저자는 일부 store 구현이 render interruption이나 transition 중 상태 분기와 관련된 테스트를 통과하지 못하는 사례를 언급한다.
글에서 가장 흥미로운 관점은 렌더링 횟수 자체보다 렌더링할 때 실제로 수행하는 작업량이 중요하다는 것이다.
React Profiler에서 component가 다시 실행됐다는 사실만 확인하고 최적화 여부를 결정하기보다, React Compiler가 재사용하는 값과 JSX, 실제 commit 시간, 사용자 입력의 응답 시간을 함께 측정해야 한다는 점을 구체적인 숫자로 보여준다.
How to build production-grade Data Modeling apps in React: A JointJS architecture walkthrough
- 저자: Zoran Jambor / JointJS
- 게시일: 2026-10-07
- 원문: https://www.jointjs.com/blog/how-to-build-production-grade-data-modeling-apps-in-react-a-jointjs-architecture-walkthrough
데이터베이스 schema designer처럼 복잡한 시각적 편집기를 React로 만들 때 그래프 UI와 실제 domain model을 어떻게 결합할 것인지 설명한 글이다.
겉으로 보기에는 테이블을 화면에 배치하고 관계선을 연결하는 다이어그램 애플리케이션이지만 실제 구현에는 훨씬 많은 제약이 있다.
사용자는 테이블 이름, column type, primary key, foreign key, nullability, default value 등을 수정할 수 있어야 한다. 테이블을 드래그하거나 column을 추가하더라도 foreign key 연결이 올바른 column을 계속 가리켜야 한다.
또한 화면에서 수정한 내용을 SQL DDL로 변환해야 하고, 반대로 SQL을 입력하면 다이어그램도 갱신할 수 있어야 한다.
이런 기능을 각각 별도의 상태로 관리하면 diagram state와 database schema가 서로 어긋나는 문제가 생기기 쉽다.
글에서 가장 먼저 설명하는 설계 선택은 그래프 자체를 단일 원본 데이터로 사용하는 것이다.
각 graph cell은 kind로 구분되는 typed data를 보유하고, table cell에는 실제 database table 정보가 들어간다.
React에서는 JointJS의 Controlled Mode를 사용해 cells를 state로 관리하고 모든 변경을 setCells를 통해 반영한다.
별도의 schema state를 동시에 유지하지 않고 schemaToCells()와 cellsToSchema()를 이용해 graph와 SQL schema 표현을 변환한다.
이 구조에서는 다이어그램이 주 상태이고 SQL은 그 상태에서 파생되는 결과물이다.
Database schema와 관련된 parsing, DDL generation, dialect mapping은 diagram library에 의존하지 않는 별도의 TypeScript module로 구성한다.
공식 예제에서는 SQL 관련 코드와 테스트가 약 2,532줄이며 해당 module에서는 JointJS를 import하지 않는다. 따라서 diagram library 없이 SQL 처리 로직만 독립적으로 테스트할 수 있다.
Foreign key 관계를 구현하는 방식도 중요하다.
일반적인 그래프 라이브러리는 노드와 노드를 연결하지만 database schema에서는 테이블 전체가 아니라 특정 column과 다른 column 사이에 연결 관계가 존재한다.
JointJS에서는 column마다 고유한 magnet ID를 부여하고 link endpoint에 table ID와 column magnet ID를 저장한다.
화면 좌표를 관계의 기준으로 사용하지 않기 때문에 테이블을 드래그하거나 크기를 변경해도 foreign key의 실제 대상은 바뀌지 않는다.
연결 규칙은 별도의 validation configuration으로 관리한다. 테이블 body에 직접 관계선을 연결하지 못하도록 하고 동일한 column pair에 중복 관계가 만들어지지 않도록 제한한다.
성능 문제도 구체적으로 다룬다.
사용자가 테이블을 드래그하면 위치 정보가 초당 수십 번 변경될 수 있다. 이때 graph state가 변경됐다는 이유만으로 SQL DDL을 매번 다시 생성하면 불필요한 계산이 반복된다.
예제에서는 useCells의 selector와 comparison 기능을 사용한다.
Graph에서 schema를 추출한 뒤 이전 schema와 비교하고 의미 있는 변경이 없으면 SQL panel을 다시 렌더링하지 않는다.
테이블 위치는 database schema의 일부가 아니므로 drag operation은 SQL regeneration을 유발하지 않는다.
이 비교가 매번 실행되는 만큼 table과 group은 immutable update를 활용해 reference equality로 비교하고, 매번 새로 만들어지는 relation은 필요한 field를 비교한다.
접근성 구현도 별도의 영역으로 다룬다.
복잡한 diagram editor는 마우스 기반 인터페이스에만 집중하기 쉽지만, 예제에서는 키보드만으로 node 추가, 관계 연결, 요소 이동을 수행할 수 있도록 설계했다.
각 node에 ARIA role과 label을 부여하고 ARIA live region을 이용해 동적인 상태 변경을 screen reader에 알린다.
대규모 schema를 위한 virtualRendering과 spatial index도 소개한다. 전자는 화면에 보이는 cell 중심으로 렌더링하고, 후자는 quad-tree를 이용해 hit testing과 containment check를 효율적으로 처리한다.
다만 이 예제는 상용 JointJS+ for React 패키지를 사용한다. Source code는 공개돼 있지만 직접 실행하려면 유료 license 또는 free trial이 필요하다. 글 자체도 JointJS 제품의 기능을 소개하는 목적을 가지고 있으므로 다른 diagram library와의 객관적인 성능 비교 자료로 볼 수는 없다.
그럼에도 실제 구현 코드와 상태 모델, relation validation, 성능 최적화, 접근성까지 구체적으로 설명하고 있어 React 기반 diagram editor, workflow builder, ERD 도구, 데이터 lineage UI를 설계할 때 참고할 만하다.
The Remix Way
- 저자: Sergio Xalambrí
- 게시일: 2026-10-02
- 원문: https://sergiodxa.com/articles/the-remix-way
개발자가 약 6개월 동안 Remix v3로 11개의 애플리케이션과 88개의 패키지를 구축하면서 정리한 설계 원칙을 설명한 글이다.
Remix v3를 단순히 React Router와 다른 frontend framework로 비교하기보다, framework가 제공하는 작은 primitive와 contract를 조합해 애플리케이션을 구성하는 방식을 중심으로 이야기를 전개한다.
처음 시작한 작업은 개인 블로그를 Remix v3로 다시 만드는 것이었다.
하지만 이 블로그는 단순한 정적 사이트가 아니다. 약 650개의 글을 Cloudflare D1에서 조회하고 RSS·Atom·JSON Feed, caching, Markdown parsing, syntax highlighting을 지원해야 했다.
기존 프로젝트는 React Router 기반 monorepo였으며 여러 애플리케이션과 약 20개의 shared package를 사용하고 있었다.
새로운 Remix 구조로 옮기면서 저자는 구현체보다 contract를 먼저 정의하는 방식에 집중했다.
예를 들어 Remix의 data-table module은 PostgreSQL, MySQL, SQLite adapter를 제공하지만 Cloudflare D1에 직접 대응하는 adapter는 없었다.
대신 Remix가 공개한 Database interface를 구현해 D1용 adapter를 만들었다. 같은 방식을 Durable Object storage에도 적용했다.
이후 caching, email, billing에도 유사한 구조를 사용했다.
Cache interface를 먼저 정의하고 테스트 환경에서는 memory implementation을, production에서는 Workers KV implementation을 연결한다.
Email도 특정 provider SDK를 애플리케이션 곳곳에서 직접 호출하지 않고 Transport contract 뒤에 감쌌다.
실제로 Resend에서 Cloudflare의 email 기능으로 provider를 변경할 때 비즈니스 로직을 수정하는 대신 transport implementation만 교체할 수 있었다고 설명한다.
Frontend component에서도 비슷한 원칙을 적용했다.
기존 React Router 애플리케이션에서는 React Aria 기반 custom UI library를 사용했지만 Remix v3에서는 remix/component를 기반으로 component를 다시 만들었다.
이때 모든 component를 client-side JavaScript로 hydration하는 대신 상호작용이 실제로 필요한 부분에만 JavaScript를 전달하도록 구성했다.
저자의 블로그는 hydrated component가 전혀 없으며, uptime monitoring 애플리케이션은 기능에 따라 일부 component만 hydration한다.
CSS에서도 framework가 제공하는 mixin을 활용해 별도의 utility layer를 만들고, container query와 color scheme 같은 플랫폼 기능을 조합했다.
후반부에서는 Web API를 우선 사용하는 방식을 설명한다.
예를 들어 modal dialog를 여는 데 별도의 JavaScript event handler를 작성하는 대신 HTML의 command="show-modal"과 commandfor attribute를 활용한다.
국제화에서는 MessageFormat 2를 사용하고 향후 표준 Intl.MessageFormat API를 사용할 수 있도록 자체 implementation을 설계했다.
Router와 background job, MCP server도 동일한 contract 중심 구조로 만든다.
Route definition과 handler mapping을 분리하면 server와 client가 route table을 공유할 수 있다. MCP tool도 결국 Request와 Response를 처리하는 handler로 만들 수 있으므로 기존 HTTP routing infrastructure 안에 통합할 수 있다.
Background job은 job definition과 input schema를 먼저 등록하고 실제 handler를 나중에 연결한다. 따라서 job enqueue 시점에도 TypeScript를 통해 입력 형태를 검증할 수 있다.
저자는 이를 보여주기 위해 공개 job board 애플리케이션을 만들었다. 목록 페이지에서는 component 하나만 hydration하며, 상세 페이지는 실제로 열었을 때 필요한 Markdown parsing을 수행한다. 해당 interactive island의 크기는 846바이트라고 설명한다.
다만 이 글은 특정 benchmark를 통해 Remix v3가 다른 framework보다 빠르다는 사실을 입증하는 자료는 아니다.
저자가 직접 관리하는 여러 애플리케이션을 기반으로 한 architecture 경험담이며, 기존 dependency를 대체하기 위해 직접 많은 package를 유지하는 선택이 모든 팀에 적합한 것도 아니다.
자체 contract가 많아질수록 추상화 설계와 유지보수에 대한 책임도 커진다.
그럼에도 framework의 추상화에 애플리케이션을 맞추기보다 Web API와 작은 contract를 중심으로 설계를 구성하는 접근을 실제 코드와 장기간의 migration 사례로 살펴볼 수 있다는 점이 흥미롭다.
Enforcing Best Practices with Jev as a Linter
- 저자: Nicolas Charpentier
- 게시일: 2026-10-01
- 원문: https://charpeni.com/blog/enforcing-best-practices-with-jev-as-a-linter
코드 리뷰에서 반복적으로 지적되는 문제를 자연어로 작성한 lint rule로 자동화하는 과정을 다룬 글이다.
저자가 경험한 문제는 개발자가 작성한 코드와 AI agent가 작성한 코드 모두에서 나타난다.
팀에서 특정한 coding convention을 정하고 PR review를 통해 여러 번 설명하더라도, 다음 PR에서 같은 패턴이 다시 등장한다.
특히 coding agent는 이전 PR에 달린 comment를 반드시 기억하거나 참고하지 않기 때문에 같은 실수를 반복하기 쉽다.
팀은 이런 문제를 줄이기 위해 과거 PR review에서 나온 best practice를 AI-REVIEW.md로 관리하고 있었다.
하지만 문서에 규칙을 적어 두는 것만으로는 자동으로 강제할 수 없다.
예를 들어 boolean 상태를 관리할 때 직접 useState와 useCallback을 조합하기보다 팀에서 만든 useBooleanState hook을 사용하도록 권장한다고 가정해 보자.
일반적인 AST 기반 linter로 이를 강제하려면 어떤 useState가 대상인지, callback이 어떤 동작을 하는지, 예외는 무엇인지 규칙을 코드로 직접 구현해야 한다.
이 규칙이 많아질수록 custom ESLint plugin이나 Oxlint plugin을 관리하는 비용도 증가한다.
저자는 이를 해결하기 위해 Jev와 oxlint-plugin-jev 를 사용했다.
Jev는 일반적인 텍스트 생성 모델과 다르게 입력에 대해 사전에 정의된 질문의 답을 판단하고 확률을 반환하는 모델이다.
Lint rule은 JSON configuration으로 정의하며 핵심 요소는 다음과 같다.
- 검사 대상인 file, function, call, JSX element 등
- 위반 여부를 판단하는 자연어 질문
- 위반으로 처리할 확률의 cutoff
- 실제 문제가 있는 위치를 찾기 위한 추가 질문
실제 규칙에서는 단순히 "useBooleanState를 사용하지 않았는가?"라고 질문하지 않는다.
useState로 boolean 상태를 생성하고, useCallback이 해당 setter를 호출하는 패턴 가운데 callback이 부수효과 없이 단순히 true 또는 false를 설정하는 경우를 대상으로 한다.
반대로 callback에 다른 작업이 포함돼 있거나 lazy initializer를 사용하거나 setter가 외부로 전달되는 경우는 제외한다.
이처럼 기존 code review 문서에 있던 예외 조건까지 자연어로 명시해 모델이 검사할 범위를 좁혔다.
위반 판정의 cutoff는 0.9로 설정했다. 저자는 모든 위반을 잡아내기보다 false positive를 줄이는 방향을 선택했다.
CI에서 잘못된 lint error가 반복되면 개발자가 검사 결과를 무시하기 시작하기 때문이다.
오류 위치를 찾는 과정에서도 문제가 있었다.
처음에는 모델이 file 전체를 검사해 위반을 찾았지만, diagnostic 위치가 항상 첫 번째 줄로 표시됐다.
400줄짜리 React component의 첫 줄에 오류가 표시되는 것은 실제 수정에 도움이 되지 않는다.
이를 해결하기 위해 저자는 plugin에 별도의 location 질문을 추가했다.
Source code에 줄 번호를 붙이고 모델에게 문제가 있는 줄을 자유롭게 생성하도록 하지 않고 제공된 줄 번호 중 하나를 선택하게 했다.
답변 후보에 없는 위치를 모델이 만들어 내는 문제를 막기 위한 선택이다.
그 결과 GitHub Actions에서 실행한 Oxlint의 결과를 PR diff에 line annotation으로 표시할 수 있게 됐다.
모델의 출력은 확정적인 오류라기보다 확률 기반 판단이므로 annotation 문구에서도 위반이 의심된다는 표현을 사용한다.
운영 비용도 공개했다.
초기 175번 실행에서 Jev 사용 비용은 총 0.35달러, 실행당 약 0.002달러였다.
같은 기간 GitHub Actions runner는 약 167분을 사용했고 비용은 약 0.67달러였다. 해당 환경에서는 실제 모델 호출보다 CI runner 비용이 더 높았다.
이는 저자가 사용한 특정 모델, 가격과 검사량에 따른 사례이므로 모든 repository에서 동일한 비용이 발생한다는 의미는 아니다.
중요한 제한도 있다.
자연어 기반 규칙은 기존 AST lint rule처럼 결과가 완전히 결정적인 것은 아니다. 질문의 표현이나 모델 버전이 바뀌면 판정 결과도 달라질 수 있다.
이를 줄이기 위해 저자는 model version을 고정하고, 정상 코드와 위반 코드뿐 아니라 경계에 있는 near-miss 사례까지 포함한 fixture test를 관리한다.
이 글은 AI를 이용해 기존 lint 도구를 대체하자는 주장보다는 기존 deterministic linter가 표현하기 어려운 팀별 규칙을 확률 기반 검사로 보완하는 운영 사례에 가깝다.
특히 AI agent가 생성하는 코드에 팀 내부의 coding convention을 지속적으로 적용하고 싶은 환경에서 참고할 만하다.
Building an evidence-grounded agentic security operations harness on Cloudflare
- 저자: Deanna Tran, Javier Castro, Jacob Crisp, Blake Darché / Cloudflare
- 게시일: 2026-10-07
- 원문: https://blog.cloudflare.com/agentic-security-operations/
Cloudflare가 Managed Defense의 보안 경고 분석 과정에 여러 AI agent를 도입하면서 단일 agent 구조를 버리고 증거 수집·검증·판단을 분리한 과정을 설명한 글이다.
보안 운영 환경에서는 한 가지 공격 시도가 여러 종류의 경고를 동시에 발생시킬 수 있다.
WAF가 특정 request를 차단하고, rate limiting이 같은 IP의 반복 요청을 감지하며, 다른 detector가 해당 traffic을 비정상적인 것으로 분류할 수 있다.
이 경고들은 서로 독립적인 사건일 수도 있고 하나의 공격을 다른 관점에서 관찰한 결과일 수도 있다.
보안 담당자는 각각의 경고를 검토하면서 실제 공격이 있었는지, 이미 차단됐는지, false positive인지, 추가 대응이 필요한지 판단해야 한다.
Cloudflare는 처음에 이런 조사 과정을 하나의 범용 AI agent에 맡겼다.
그러나 초기 prototype에서는 세 가지 문제가 반복됐다.
첫 번째는 탐지 결과를 실제 공격의 증거로 과도하게 해석하는 문제였다.
탐지 규칙이 실행됐다는 사실은 의심스러운 상황이 발생했다는 의미일 뿐, 공격이 실제로 성공했다는 뜻은 아니다.
두 번째는 조사 범위가 달라지는 문제였다.
모델이 다른 고객의 계정이나 잘못된 시간 범위의 데이터를 조회하는 것처럼, 자연어 지시만으로는 조사 대상의 경계를 완전히 보장할 수 없었다.
세 번째는 데이터를 찾지 못한 상황과 조회 자체가 실패한 상황을 구분하지 못하는 문제였다.
외부 threat intelligence 요청이 timeout됐는데 결과에 아무 값이 없다는 이유로 위협이 존재하지 않는다고 판단하면 잘못된 결론으로 이어진다.
Cloudflare가 선택한 해결 방식은 AI agent에게 더 상세한 prompt를 작성하는 것이 아니었다.
대신 증거 수집과 조사 범위 강제를 애플리케이션 코드로 이동했다.
모델을 호출하기 전에 deterministic code가 미리 정의된 reconnaissance workflow를 실행한다.
이 단계에서는 고객 identity, 이전 탐지 기록, traffic baseline, 적용된 security control, 실제 차단 결과, network observation을 수집한다.
각 결과에는 원본 source, version, timestamp를 함께 기록한다.
이렇게 만들어진 evidence snapshot은 이후 분석에서 재사용할 수 있다.
모델이 직접 실시간 데이터를 조회하는 방식에서는 두 번의 분석이 서로 다른 입력을 사용할 수 있지만, 고정된 snapshot을 이용하면 동일한 증거를 다시 분석할 수 있다.
이후에는 Cloudflare의 오픈소스 decision model인 Clef를 이용해 초기 triage를 수행한다.
반복적으로 발생하는 알려진 false positive나 정상 traffic 패턴과 일치하는 경고는 깊은 조사의 우선순위를 낮춘다.
추가 분석이 필요한 경고는 네 가지 전문 agent에게 전달된다.
- Traffic Analysis: Request 행동, 과거 변화, 실제 enforcement 결과 분석
- Customer Context: 고객의 이전 경고와 보안 담당자의 판단 기록 분석
- Global Telemetry: Cloudflare 전체 네트워크의 집계 정보를 통한 비교
- Threat Intelligence: 허용된 IOC와 위협 정보 분석
이 agent들은 병렬로 실행되며 각각 제한된 데이터만 받는다.
Global Telemetry agent에는 다른 고객의 개별 기록이나 identity가 전달되지 않고 개인정보를 보호하는 집계 데이터만 제공된다.
각 agent가 반환하는 결과도 자유로운 자연어 문서가 아니라 정해진 구조를 가진 typed finding이다.
최종 synthesis agent는 이 결과를 하나의 advisory로 결합하지만, 새로운 증거를 임의로 조회하거나 사전에 허용하지 않은 classification을 선택할 수 없다.
증거 검증도 application code가 수행한다.
각 finding은 사용한 증거를 인용해야 하며, 코드에서는 해당 증거가 실제 evidence package에 존재하는지, 올바른 조사 범위에 속하는지 확인한다.
검증에 실패한 판단은 수정하거나 분석의 제한사항으로 기록한다.
Cloudflare는 이 과정을 다음과 같은 infrastructure 위에 구성했다.
- Workers: 증거 수집과 결과 검증
- Workflows: 단계별 실행과 실패 이후 재개
- D1: 조사 및 advisory 상태 저장
- R2: 증거와 분석에 필요한 artifact 저장
- Durable Objects: 대화형 조사 session의 상태 유지
특히 Workflows를 이용하면 어느 단계에서 실패하더라도 이미 검증을 통과한 이전 단계의 결과를 다시 사용할 수 있다.
데이터가 불완전할 때는 세 가지 상태를 구분한다.
- 확인하지 않음
- 확인했지만 일치하는 결과가 없음
- 확인했고 부재를 뒷받침하는 증거가 있음
이 차이는 보안 분석에서 중요하다.
예를 들어 global telemetry를 조회할 수 없었다면 특정 고객의 traffic이 평소와 다르다는 사실은 설명할 수 있어도, 동일한 공격이 인터넷 전체에서 발생하는지까지 결론 내릴 수는 없다.
증거가 충분하지 않은 경우에는 공격 유형을 분류하거나 대응 조치를 권고하지 않도록 설계했다.
최종적인 조치 역시 사람의 책임으로 남겨 뒀다.
AI agent는 rate limiting rule이나 WAF configuration 변경을 제안할 수 있지만, 실제 대응 여부와 범위는 Managed Defense 담당자가 확인한다.
이 글에는 새로운 시스템의 정확도나 비용 절감률을 검증할 수 있는 정량 benchmark가 제공되지 않는다. 따라서 여러 agent를 사용했다는 사실만으로 기존 구조보다 객관적으로 더 정확하다고 단정할 수는 없다.
그럼에도 LLM을 실제 운영 시스템에 도입할 때 무엇을 모델에 맡기고 무엇을 deterministic code로 강제해야 하는지를 구체적으로 보여준다.
특히 multi-tenant 권한 경계, 증거의 출처 관리, 재현 가능한 평가, 실패와 부재의 구분, 사람이 최종 승인하는 구조는 보안 분야뿐 아니라 복잡한 backend automation이나 AI agent workflow를 설계할 때도 참고할 만하다.