JavaScript JWT 디코딩 atob & jose

·JavaScript Performance Engineer·검토자Sophie Laurent·게시일

무료 JWT 디코더을 브라우저에서 직접 사용하세요 — 설치 불필요.

JWT 디코더 온라인으로 사용하기 →

인증 플로우를 구현하다 보면 결국 같은 지점에 도달합니다. 쿠키, 헤더, 또는 OAuth 콜백 URL에 JWT가 있고 그 안의 내용을 읽어야 하는 상황입니다. JavaScript JWT 디코더 npm 패키지가 필요하지 않습니다. 토큰의 헤더와 페이로드는 단순히 Base64url로 인코딩된 JSON이며, 브라우저와 Node.js 모두 이를 디코딩하는 데 필요한 모든 것을 내장하고 있습니다. 이 가이드는 JWT를 위한 전체 JavaScript 텍스트 디코더 파이프라인을 다룹니다: 토큰 분리, base64url 을 표준 Base64로 정규화, atob() TextDecoder 를 사용한 올바른 UTF-8 처리, Node.js Buffer.from(),jose를 통한 서명 검증, 그리고 개발자들이 매일 겪는 일반적인 실수들입니다. 빠른 일회성 검사를 원한다면 온라인 JWT 디코더 를 사용해 보세요. 모든 예제는 ES2020+ Node.js 18+를 대상으로 합니다.

  • JWT를 "."으로 분리하세요 — 인덱스 0은 헤더, 1은 페이로드, 2는 서명입니다.
  • atob()는 Base64를 디코딩하지만 UTF-8이 아닌 Latin-1을 반환합니다. ASCII가 아닌 클레임에는 TextDecoder 또는 Buffer.from()을 사용하세요.
  • Buffer.from(segment, "base64url")은 Node.js에서 base64url을 네이티브로 처리합니다 — 수동 문자 치환이 필요 없습니다.
  • 디코딩은 검증이 아닙니다. 서버 측에서 서명을 확인하지 않고 디코딩된 JWT의 클레임을 신뢰하지 마세요.
  • jose 라이브러리는 두 가지를 모두 수행합니다: HS256/RS256/ES256 서명을 검증하고 한 번의 호출로 디코딩된 페이로드를 반환합니다.

JWT 디코딩이란?

JSON Web Token은 점으로 구분된 세 개의 Base64url 인코딩 세그먼트입니다. 첫 번째 세그먼트는 헤더, 두 번째는 페이로드(실제로 필요한 클레임), 세 번째는 암호화 서명입니다. 헤더는 토큰 자체를 설명하는 작은 JSON 객체입니다. 가장 중요한 필드는 alg — 서명 알고리즘(예: HS256, RS256, ES256)입니다.typ 필드는 거의 항상 "JWT"이며, 선택적인 kid 필드는 토큰에 서명하는 데 사용된 키를 식별합니다 — ID 제공자가 키를 교체하고 여러 공개 키가 있는 JWKS 엔드포인트를 게시할 때 중요합니다.

페이로드에는 클레임이 담겨 있습니다. RFC 7519는 일곱 가지 등록된 클레임 이름을 정의합니다: sub (주체 — 보통 사용자 ID), iss (발급자 — 인증 서버 URL), aud (대상 — 토큰이 의도된 API), iat (발급 시간 타임스탬프), exp (만료 타임스탬프), nbf (not-before 타임스탬프), 그리고 jti (JWT ID — 재생 공격 방지에 사용)입니다. 모든 타임스탬프는 밀리초가 아닌 Unix epoch 초 단위입니다. 서명 세그먼트는 원시 바이너리 — 키가 있는 HMAC 다이제스트 또는 비대칭 디지털 서명입니다. 다른 세그먼트와 마찬가지로 Base64url로 인코딩되지만, 그 바이트는 JSON이 아니며 사람이 읽을 수 있는 구조가 없습니다.

실제로 JavaScript에서 JWT를 디코딩하는 세 가지 일반적인 이유가 있습니다. 첫째, 디버깅: OAuth 플로우나 테스트 환경에서 받은 토큰이 있고 클레임이 인증 서버가 발급해야 할 것과 일치하는지 확인하고 싶을 때입니다. 둘째, 추가 API 호출 없이 토큰 페이로드에서 클라이언트 측의 사용자 표시 목적으로 클레임을 읽을 때 — 로그인한 사용자의 이름, 아바타 URL, 또는 역할 배지를 표시할 때입니다. 셋째, 갱신을 시도하기 전에 만료 여부를 확인할 때:exp 가 다음 60초 이내에 만료된다면, 401 응답을 기다리지 않고 다음 API 호출 전에 자동 갱신을 트리거합니다.

디코딩은 토큰이 유효하거나 변조되지 않았는지 확인하지 않습니다. 그것은 HMAC 시크릿 또는 RSA/ECDSA 공개 키가 필요한 검증이라는 별도의 작업입니다. 누구나 JWT를 디코딩할 수 있습니다. 올바른 키를 가진 사람만이 검증할 수 있습니다. 이 구분은 디코딩된 클레임이 표시되지만 검증된 백엔드 확인 없이는 권한 결정에 신뢰되어서는 안 되는 클라이언트 측 인증 플로우를 구축할 때 많은 개발자들을 혼동시킵니다.

Before · json
After · json
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxMTYxMDAwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
// 헤더
{ "alg": "HS256" }

// 페이로드
{
  "sub": "usr_921f",
  "role": "admin",
  "iat": 1711610000
}

atob() + TextDecoder — 브라우저 내장 JWT 디코드

JWT를 디코딩하는 브라우저 내장 파이프라인은 네 단계입니다. 첫째, 토큰 문자열을 "." 으로 분리하여 세 세그먼트를 얻습니다. 둘째, base64url 세그먼트를 - + 로, _ /로 치환하고 길이가 4의 배수가 될 때까지 = 문자로 패딩하여 정규화합니다. 셋째, atob() 를 호출하여 Base64를 바이너리 문자열로 디코딩합니다. 넷째, TextDecoder를 사용하여 바이너리 문자열을 올바른 UTF-8로 변환합니다. 마지막 단계가 중요한 이유는 atob() 가 Latin-1을 반환하기 때문입니다. 다중 바이트 문자 — 이모지, CJK 텍스트, Latin-1 범위를 넘는 악센트 문자 — 는 JavaScript 텍스트 디코더 단계 없이는 깨져서 나옵니다.

JavaScript — 최소 JWT 디코드
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxMTYxMDAwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";

function decodeJwtPayload(jwt) {
  const base64Url = jwt.split(".")[1];
  const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
  const binary = atob(padded);
  const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
  const json = new TextDecoder("utf-8").decode(bytes);
  return JSON.parse(json);
}

console.log(decodeJwtPayload(token));
// { sub: "usr_921f", role: "admin", iat: 1711610000 }

패딩 단계는 놓치기 쉽습니다. JWT는 JWT 사양(RFC 7515)이 패딩 없는 base64url을 정의하기 때문에 Base64url 세그먼트에서 후행 = 문자를 제거합니다. 하지만 일부 브라우저 엔진의 atob() 는 입력 길이가 4로 나누어지지 않으면 InvalidCharacterError 를 던집니다. padEnd() 로 방어적으로 패딩하면 모든 환경에서 이 엣지 케이스를 피할 수 있습니다. 다음은 헤더와 페이로드를 각각 별도의 객체로 디코딩하는 재사용 가능한 버전입니다:

JavaScript — 헤더와 페이로드 디코드
function decodeBase64Url(segment) {
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
  const binary = atob(padded);
  const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
  return new TextDecoder("utf-8").decode(bytes);
}

function decodeJwt(token) {
  const [headerB64, payloadB64] = token.split(".");
  return {
    header: JSON.parse(decodeBase64Url(headerB64)),
    payload: JSON.parse(decodeBase64Url(payloadB64)),
  };
}

const { header, payload } = decodeJwt(token);
console.log("Algorithm:", header.alg);   // "HS256"
console.log("Subject:", payload.sub);     // "usr_921f"
console.log("Role:", payload.role);       // "admin"

이 두 함수를 작성했다면, 파일 여러 곳에 로직을 복사-붙여넣기하는 대신 공유 유틸리티 모듈에 배치하는 것이 좋습니다. src/lib/jwt.ts 또는 utils/jwt-decode.ts 파일에 타입이 지정된 반환 형태를 두면 코드베이스 전체에서 의도가 명확해집니다. TypeScript에서 반환 타입을 { header: JwtHeader; payload: JwtPayload } 로 지정할 수 있으며, 여기서 JwtHeader alg, typ, 선택적 kid를 포함하고, JwtPayload 는 RFC 7519 등록 클레임을 커스텀 클레임을 위한 인덱스 시그니처로 확장합니다. 디코딩 로직을 중앙화하면 나중에 오류 처리(잘못된 형식의 세그먼트 포착)나 텔레메트리 (디코딩 실패 로깅)를 추가하고 싶을 때 한 곳만 업데이트하면 됩니다.

참고:TextDecoder 단계는 이 파이프라인을 ASCII가 아닌 클레임에 대해 안전하게 만드는 부분입니다. 이 단계 없이는 atob()가 다중 바이트 UTF-8 시퀀스가 문자들 사이로 분리되는 Latin-1 문자열을 반환합니다. 이모지나 CJK 텍스트 대신 깨진 문자가 보일 것입니다. 항상 atob() 이후에 new TextDecoder("utf-8")를 통과시키세요.

다중 바이트 문자가 포함된 UTF-8 JWT 클레임 디코딩

JWT 페이로드는 base64url로 인코딩된 UTF-8 JSON입니다. 대부분의 페이로드에는 사용자 ID와 타임스탬프 같은 ASCII 전용 필드가 포함되어 있어서, 개발자들은 atob() 가 UTF-8이 아닌 Latin-1을 반환한다는 사실을 알아채지 못합니다. 문제는 클레임에 이모지, 한국어·한자·일본어 문자, 키릴 문자, 또는 U+00FF 이상의 코드 포인트가 포함되는 순간 나타납니다. JavaScript decode UTF-8 패턴은 바이너리 문자열을 먼저 바이트 배열로 변환한 다음 TextDecoder를 통과시켜야 합니다.

JavaScript — JWT 페이로드에서 한국어 이름과 함께하는 UTF-8 라운드트립
// JWT 페이로드에 한국어 이름이 있는 경우 시뮬레이션
const payloadObj = {
  sub: "usr_e821",
  display_name: "김민준",
  team: "플랫폼 🚀",
  region: "ap-northeast-1"
};

// 인코딩: 객체 → JSON → UTF-8 바이트 → base64url
const jsonStr = JSON.stringify(payloadObj);
const utf8Bytes = new TextEncoder().encode(jsonStr);
const base64 = btoa(String.fromCharCode(...utf8Bytes))
  .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

// 디코딩: base64url → base64 → 바이너리 문자열 → 바이트 → UTF-8 문자열
const base64Std = base64.replace(/-/g, "+").replace(/_/g, "/");
const binary = atob(base64Std);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
const decoded = new TextDecoder("utf-8").decode(bytes);
const result = JSON.parse(decoded);

console.log(result.display_name); // "김민준" — 올바름
console.log(result.team);         // "플랫폼 🚀" — 올바름

오래된 코드베이스에서 decodeURIComponent 와 퍼센트 인코딩 트릭을 조합한 레거시 폴백 패턴을 볼 수 있습니다. 이 JavaScript decodeURIComponent 방법은 각 바이트를 퍼센트-16진수 쌍으로 재인코딩하고 decodeURIComponent 가 다중 바이트 UTF-8 시퀀스를 재조합하기 때문에 작동합니다:

JavaScript — UTF-8을 위한 decodeURIComponent 폴백
function decodeBase64UrlLegacy(segment) {
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const binary = atob(base64);
  // 각 문자를 %XX 16진수로 변환하면 decodeURIComponent가 UTF-8을 재조합
  const utf8 = decodeURIComponent(
    binary.split("").map(c =>
      "%" + c.charCodeAt(0).toString(16).padStart(2, "0")
    ).join("")
  );
  return utf8;
}

// TextDecoder 없이 ASCII가 아닌 클레임에서 작동
const payload = decodeBase64UrlLegacy(token.split(".")[1]);
console.log(JSON.parse(payload));
경고:오래된 JWT 유틸리티 코드에서 decodeURIComponent(escape(atob(segment))) 패턴을 볼 수 있습니다. escape() 함수는 deprecated되었으며 비표준입니다. 위에 나온 TextDecoder 방법으로 교체하세요. JavaScript unescape 디코더 패턴도 같은 문제를 가지고 있습니다: unescape()는 deprecated됩니다. 두 함수 모두 미래 JavaScript 엔진에서 제거될 수 있습니다.

JWT 디코드 파이프라인 — 단계별 참조

브라우저 내장 JWT 디코드 파이프라인의 각 단계와 사용된 JavaScript API, 생성 결과:

파라미터 / 단계
타입
설명
token.split(".")
string[]
JWT를 [헤더, 페이로드, 서명] 세그먼트로 분리
base64url → base64
string replace
- 를 + 로, _ 를 / 로 치환하고 길이가 4의 배수가 되도록 = 로 패딩
atob(base64)
string
표준 Base64 문자열을 바이너리 문자열(Latin-1)로 디코딩
TextDecoder("utf-8")
TextDecoder
원시 바이트의 Uint8Array를 올바른 UTF-8 문자열로 변환
JSON.parse()
object
JSON 문자열을 JavaScript 객체로 파싱

Node.js 동등 코드는 2~4단계를 단일 호출로 압축합니다: Buffer.from(segment, "base64url").toString("utf-8")."base64url" 인코딩 옵션이 알파벳 변환과 패딩을 내부적으로 처리합니다.

Buffer.from() — Node.js JWT용 문자열 디코더

Node.js에는 훨씬 간단한 방법이 있습니다. Buffer 클래스는 "base64url" 인코딩을 직접 받아들이므로, 수동 문자 치환과 패딩을 건너뜁니다. 이것은 서버 측 코드를 위한 JavaScript 문자열 디코더 경로입니다. 한 줄로 JWT 세그먼트를 UTF-8 문자열로 변환하며, 추가 단계 없이 다중 바이트 문자를 올바르게 처리합니다.

Node.js 18+ — Buffer로 JWT 디코드
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsIm9yZyI6ImFjbWUtY29ycCIsInJvbGUiOiJiaWxsaW5nIiwiaWF0IjoxNzExNjEwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

function decodeJwt(jwt) {
  const segments = jwt.split(".");
  return {
    header: JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8")),
    payload: JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8")),
  };
}

const { header, payload } = decodeJwt(token);
console.log(header);
// { alg: "HS256" }
console.log(payload);
// { sub: "usr_921f", org: "acme-corp", role: "billing", iat: 1711610000 }

모든 Node.js 프로젝트에서 제가 선택하는 방법입니다. 더 짧고, 빠르고, 이미 UTF-8을 올바르게 처리합니다. TextDecoder 가 필요 없고, 문자 치환도 없고, 패딩 계산도 없습니다. Buffer 클래스는 base64url 알파벳을 네이티브로 처리하는 JavaScript 문자열 디코더로, 문자 치환과 관련된 전체 버그 종류를 제거합니다. 브라우저와 Node.js 양쪽에서 실행해야 하는 코드라면, 런타임에 환경을 감지하는 동형 래퍼 함수를 위한 FAQ를 하단에서 확인하세요.

다음은 일반적인 JWT 클레임을 추출하고 타임스탬프를 읽기 가능한 날짜로 변환하는 더 완전한 예제로, 미들웨어와 API 라우트 핸들러에서 가장 자주 사용하는 패턴입니다:

Node.js — 실용적인 JWT 클레임 추출
function inspectToken(token) {
  const segments = token.split(".");
  if (segments.length !== 3) {
    throw new Error("유효한 JWT가 아닙니다 — 점으로 구분된 3개의 세그먼트가 필요합니다");
  }

  const header = JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8"));
  const payload = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8"));

  const inspection = {
    algorithm: header.alg,
    tokenType: header.typ || "JWT",
    subject: payload.sub,
    issuer: payload.iss || "(설정되지 않음)",
    audience: payload.aud || "(설정되지 않음)",
    issuedAt: payload.iat ? new Date(payload.iat * 1000).toISOString() : "(설정되지 않음)",
    expiresAt: payload.exp ? new Date(payload.exp * 1000).toISOString() : "(만료 없음)",
    isExpired: payload.exp ? payload.exp < Math.floor(Date.now() / 1000) : false,
    customClaims: Object.keys(payload).filter(
      k => !["sub", "iss", "aud", "iat", "exp", "nbf", "jti"].includes(k)
    ),
  };

  return inspection;
}

console.log(inspectToken(process.env.ACCESS_TOKEN));
// {
//   algorithm: "RS256",
//   tokenType: "JWT",
//   subject: "usr_921f",
//   issuer: "https://auth.internal",
//   audience: "billing-api",
//   issuedAt: "2026-03-10T14:00:00.000Z",
//   expiresAt: "2026-03-10T15:00:00.000Z",
//   isExpired: true,
//   customClaims: ["role", "scope", "org"]
// }

프로덕션 Node.js 서비스에서 Buffer.from() 디코딩 패턴은 세 가지 반복적인 장소에 나타납니다. 첫 번째는 요청 로깅 미들웨어입니다: 인증 서버에 추가 네트워크 왕복 없이 모든 구조화된 로그 항목에 userId org 를 첨부하기 위해 들어오는 Authorization 헤더를 디코딩합니다. 두 번째는 디버깅입니다: 테스트 어설션을 작성하기 전에 올바른 스코프가 발급되었는지 확인하기 위해 개발 중에 디코딩된 토큰 클레임을 콘솔에 출력합니다. 세 번째는 API 게이트웨이에서 사전 토큰 갱신입니다. 토큰을 업스트림으로 전달하고 요청 중간에 토큰이 만료될 때 다운스트림 서비스가 401을 반환하게 하는 대신, 게이트웨이는 엣지에서 토큰을 디코딩하고 exp 클레임을 읽고 만료가 다음 30초 이내에 있으면 갱신을 트리거합니다. 이는 재현하기 어렵고 디버깅하기 성가신 일시적 인증 실패 종류를 제거합니다.

참고:"base64url" 인코딩은 Node.js 15.7.0에서 추가되었습니다. Node.js 14 이하를 사용해야 한다면 Buffer.from(segment.replace(/-/g, "+").replace(/_/g, "/"), "base64") 로 폴백하세요. 같은 방식으로 작동하지만 수동 문자 교체가 필요합니다.

파일 및 API 응답에서 JWT 디코딩

두 가지 시나리오가 자주 등장합니다. 첫 번째는 로컬 파일에서 JWT 읽기입니다: 개발 중 저장된 토큰, 테스트 픽스처, 또는 사후 분석을 위해 인시던트 중에 덤프된 파일입니다. 두 번째는 HTTP 응답에서 JWT 추출입니다. 일반적으로 OAuth 토큰 응답 본문의 access_token 필드 또는 Authorization 헤더입니다. 잘못된 형식의 토큰, 잘린 파일, 네트워크 오류는 일상적인 현실이므로 두 경우 모두 오류 처리가 필요합니다. 지난 주에 유효했던 토큰에 복사-붙여넣기로 후행 공백이나 개행 문자가 있을 수 있습니다. 인증 서버가 오류 페이지를 반환했다면 응답 본문이 JSON 대신 HTML일 수 있습니다.

파일에서 JWT 읽기 (Node.js)

Node.js — 오류 처리와 함께 파일에서 JWT 디코딩
import { readFileSync } from "node:fs";

function decodeJwtFromFile(filePath) {
  const raw = readFileSync(filePath, "utf-8").trim();
  const segments = raw.split(".");

  if (segments.length !== 3) {
    throw new Error(`유효하지 않은 JWT: 3개의 세그먼트를 예상했지만 ${segments.length}개를 받았습니다`);
  }

  try {
    return {
      header: JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8")),
      payload: JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8")),
    };
  } catch (err) {
    throw new Error(`${filePath}에서 JWT 디코딩 실패: ${err.message}`);
  }
}

try {
  const { header, payload } = decodeJwtFromFile("./test-fixtures/access-token.txt");
  console.log("Algorithm:", header.alg);
  console.log("Expires:", new Date(payload.exp * 1000).toISOString());
} catch (err) {
  console.error(err.message);
}

API 응답에서 JWT 추출 (fetch)

JavaScript — API 응답에서 JWT 디코딩
async function fetchAndDecodeToken(loginUrl, credentials) {
  const response = await fetch(loginUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(credentials),
  });

  if (!response.ok) {
    throw new Error(`로그인 실패: ${response.status} ${response.statusText}`);
  }

  const { access_token } = await response.json();
  if (!access_token || access_token.split(".").length !== 3) {
    throw new Error("응답에 유효한 JWT가 포함되어 있지 않습니다");
  }

  const payload = access_token.split(".")[1];
  const json = Buffer.from(payload, "base64url").toString("utf-8");
  return JSON.parse(json);
}

// 사용법
try {
  const claims = await fetchAndDecodeToken(
    "https://auth.internal/oauth/token",
    { username: "deploy-bot", password: process.env.DEPLOY_TOKEN }
  );
  console.log("Token subject:", claims.sub);
  console.log("Token scopes:", claims.scope);
  console.log("Expires at:", new Date(claims.exp * 1000).toISOString());
} catch (err) {
  console.error("Token decode error:", err.message);
}

커맨드라인 JWT 디코딩

때로는 스크립트를 작성하지 않고 터미널에서 토큰을 빠르게 확인하고 싶을 때가 있습니다. Node.js는 대부분의 개발자 머신에서 사용 가능하므로 한 줄짜리 명령어가 잘 작동합니다. jq 가 예쁘게 출력을 처리합니다.

bash — 터미널에서 JWT 페이로드 디코딩
# Node.js 한 줄짜리로 JWT 페이로드 디코딩
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiJ9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" \
  | cut -d. -f2 \
  | node -e "process.stdin.on('data', d => console.log(JSON.parse(Buffer.from(d.toString().trim(), 'base64url').toString('utf-8'))))"

# jq로 파이프하여 예쁜 출력
echo "$JWT_TOKEN" | cut -d. -f2 \
  | node -e "process.stdin.on('data', d => process.stdout.write(Buffer.from(d.toString().trim(), 'base64url').toString('utf-8')))" \
  | jq .

# 헤더와 페이로드 모두 디코딩
echo "$JWT_TOKEN" | node -e "
  process.stdin.on('data', d => {
    const parts = d.toString().trim().split('.');
    console.log('Header:', JSON.parse(Buffer.from(parts[0], 'base64url').toString()));
    console.log('Payload:', JSON.parse(Buffer.from(parts[1], 'base64url').toString()));
  });
"

Node.js 없이 순수 bash를 선호한다면 tr로 base64url 문자를 수정한 후 base64 -d 로 세그먼트를 파이프하세요:

bash — Node.js 없이 순수 bash JWT 디코딩
# 순수 bash: Node.js 없이 JWT 페이로드 디코딩
echo "$JWT_TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

# macOS 변형 (base64 -D 대신 -d)
echo "$JWT_TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -D 2>/dev/null | jq .

터미널 없이 빠른 시각적 검사가 필요하다면, 토큰을 ToolDeck JWT 디코더 에 붙여넣으면 색상으로 구분된 클레임 레이블과 만료 상태가 포함된 세 세그먼트의 나란히 보기 분석을 제공합니다.

jose — 하나의 라이브러리로 검증과 디코딩

프로덕션 인증 미들웨어에는 단순 디코딩이 아닌 서명 검증이 필요합니다. jose 라이브러리가 최선의 옵션입니다. Node.js와 브라우저(Web Crypto API를 통해) 양쪽에서 작동하고, HS256, RS256, ES256, EdDSA, JWE(암호화 토큰)을 지원하며, 네이티브 의존성이 없습니다. npm install jose로 설치하세요.

JavaScript — jose: HS256 토큰 검증
import * as jose from "jose";

const secret = new TextEncoder().encode("k8s-webhook-signing-secret-2026");
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInNjb3BlIjoiYmlsbGluZzpyZWFkIiwiaWF0IjoxNzExNjEwMDAwLCJleHAiOjE3MTE2MTM2MDB9.abc123";

try {
  const { payload, protectedHeader } = await jose.jwtVerify(token, secret);
  console.log("Algorithm:", protectedHeader.alg); // "HS256"
  console.log("Subject:", payload.sub);            // "usr_921f"
  console.log("Scope:", payload.scope);            // "billing:read"
} catch (err) {
  if (err.code === "ERR_JWT_EXPIRED") {
    console.error("Token expired at:", err.payload.exp);
  } else {
    console.error("Verification failed:", err.message);
  }
}
JavaScript — jose: JWKS 엔드포인트로 RS256 검증
import * as jose from "jose";

// ID 제공자에서 공개 키 셋 가져오기
const jwks = jose.createRemoteJWKSet(
  new URL("https://auth.internal/.well-known/jwks.json")
);

const token = req.headers.authorization?.split(" ")[1];
if (!token) {
  return res.status(401).json({ error: "Missing token" });
}

try {
  const { payload } = await jose.jwtVerify(token, jwks, {
    issuer: "https://auth.internal",
    audience: "billing-api",
  });
  // payload.sub, payload.scope 등이 이제 검증됨
  req.userId = payload.sub;
} catch (err) {
  return res.status(401).json({ error: "Invalid token" });
}

jose 와 오래된 jsonwebtoken 패키지 중 선택할 때 핵심 차이점은 런타임 범위입니다. jsonwebtoken 은 Node.js 전용입니다 — crypto 빌트인에 의존하며 브라우저용으로 번들링되지 않습니다. jose 는 완전히 동형(isomorphic)입니다: 모든 최신 브라우저, Node.js 16+, Deno, Bun, Cloudflare Workers에서 사용 가능한 Web Crypto API를 사용합니다. 인증 로직이 Next.js 미들웨어 파일(Edge Runtime에서 실행), Cloudflare Worker, 또는 서버와 클라이언트 코드 양쪽에서 임포트되는 공유 유틸리티에 있다면, jose 가 올바른 선택입니다. 네이티브 의존성이 없고 빌드 단계 없이 설치됩니다. jsonwebtoken 은 더 넓은 서명 헬퍼 생태계가 필요하고 엣지 환경에서 코드를 실행할 계획이 없는 순수 Node.js 서버 애플리케이션에는 여전히 합리적입니다. 2026년 새 프로젝트에서는 특별한 이유가 없는 한 기본적으로 jose를 선택하세요.

검증 없이 디코딩만 필요하다면, jose는 jose.decodeJwt(token) 으로 페이로드를 반환하고 jose.decodeProtectedHeader(token) 으로 헤더를 반환하는 편의 함수를 제공합니다. 이 함수들은 Base64url 디코딩을 내부적으로 처리합니다. 하지만 jose를 선택하는 이유는 검증 없이 디코딩해서는 안 되는 경우가 드물기 때문입니다. 클라이언트 측에서 사용자의 display_name이나 아바타 URL을 토큰 클레임에서 표시하기만 한다면 디코딩 전용도 괜찮습니다. 서버 측에서는 항상 검증하세요. 서명을 확인하지 않고 접근 제어 결정에 JWT 클레임을 디코딩하는 프로덕션 시스템을 본 적이 있으며, 그것은 JWT 형식을 이해하는 공격자에게 열린 문입니다.

JavaScript — 디코딩 전용 시나리오를 위한 jose.decodeJwt
import * as jose from "jose";

// 디코딩 전용: 시크릿 불필요, 검증 없음
const payload = jose.decodeJwt(token);
console.log(payload.sub);   // "usr_921f"
console.log(payload.scope); // "billing:read"

const header = jose.decodeProtectedHeader(token);
console.log(header.alg);    // "HS256"
console.log(header.typ);    // "JWT"

// 검증 없이 만료 확인 (클라이언트 측 표시)
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
  console.log("토큰이 만료되었습니다 — 로그인 페이지로 리디렉션");
}

구문 강조와 함께하는 터미널 출력

Node.js CLI 도구에서 JWT 토큰을 디버깅하거나 인시던트 중에는 색상으로 구분된 출력이 큰 차이를 만듭니다. chalk 라이브러리와 JSON.stringify 조합이 잘 작동합니다. npm install chalk로 설치하세요.

Node.js — 색상이 있는 JWT 디코딩 출력
import chalk from "chalk";

function printJwt(token) {
  const segments = token.split(".");
  if (segments.length !== 3) {
    console.error(chalk.red("유효하지 않은 JWT: 3개의 세그먼트가 필요합니다"));
    return;
  }

  const header = JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8"));
  const payload = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8"));

  console.log(chalk.bold.cyan("\n=== JWT Header ==="));
  console.log(chalk.gray(JSON.stringify(header, null, 2)));

  console.log(chalk.bold.green("\n=== JWT Payload ==="));
  console.log(chalk.gray(JSON.stringify(payload, null, 2)));

  // 만료 상태 강조
  if (payload.exp) {
    const expiresAt = new Date(payload.exp * 1000);
    const isExpired = expiresAt < new Date();
    console.log(
      chalk.bold("\n만료:"),
      isExpired
        ? chalk.red(`만료됨: ${expiresAt.toISOString()}`)
        : chalk.green(`유효: ${expiresAt.toISOString()}까지`)
    );
  }

  console.log(chalk.dim("\nSignature: " + segments[2].substring(0, 20) + "..."));
}

printJwt(process.argv[2]);
// 실행: node jwt-debug.mjs "eyJhbGci..."
참고:색상 출력은 터미널 전용입니다. 로그 파일, API 응답, 또는 데이터베이스 필드에 JWT 클레임을 쓸 때 chalk를 사용하지 마세요. ANSI 이스케이프 코드가 터미널이 아닌 컨텍스트에서 깨진 문자로 나타납니다.

대용량 로그 파일에서 JWT 처리

현대적인 API 인프라는 NDJSON 형식으로 구조화된 액세스 로그를 생성합니다 — 줄당 하나의 JSON 객체이며, 각 줄에는 요청 경로, 응답 상태, 지연 시간, 디코딩되거나 원시 Authorization 헤더가 포함됩니다. 사용량이 많은 서비스에서 이 파일들은 빠르게 증가합니다: 분당 10,000건의 요청을 처리하는 게이트웨이는 하루에 1,400만 개 이상의 로그 항목을 생성합니다. 보안 및 컴플라이언스 사용 사례에서는 사후에 이 파일들을 스캔해야 하는 경우가 많습니다 — 손상된 서비스 계정이 만든 모든 요청 식별(사후 인시던트 분석), 특정 사용자의 토큰이 데이터 접근 창 이전에 만료되었는지 확인(컴플라이언스 감사), 또는 유지보수 창 동안 민감한 엔드포인트에 접근한 모든 주체 추출. 단일 로그 파일이 수 기가바이트를 넘을 수 있으므로 readFileSync로 메모리에 로드하는 것은 실용적이지 않습니다. Node.js readline 스트림은 일정한 메모리 오버헤드로 파일을 한 줄씩 처리하므로, 일반 개발자 노트북에서 임의로 큰 로그를 스캔할 수 있습니다.

단일 JWT는 거의 몇 킬로바이트를 넘지 않으므로 개별 JWT에서 "파일이 너무 커서 메모리에 올릴 수 없다" 문제는 발생하지 않습니다. 실제로 발생하는 시나리오는 JWT 토큰을 위한 대용량 액세스 로그나 감사 추적을 스캔하고, 각각을 디코딩하고, 특정 클레임을 추출하는 것입니다. Node.js 스트림은 전체 파일을 로드하지 않고 이를 처리합니다.

Node.js — NDJSON 로그 스트림에서 내장된 JWT 디코딩
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";

async function scanLogsForExpiredTokens(logPath) {
  const fileStream = createReadStream(logPath, { encoding: "utf-8" });
  const rl = createInterface({ input: fileStream, crlfDelay: Infinity });

  let lineCount = 0;
  let expiredCount = 0;
  const nowSeconds = Math.floor(Date.now() / 1000);

  for await (const line of rl) {
    lineCount++;
    try {
      const entry = JSON.parse(line);
      if (!entry.authorization_token) continue;

      const segments = entry.authorization_token.split(".");
      if (segments.length !== 3) continue;

      const payload = JSON.parse(
        Buffer.from(segments[1], "base64url").toString("utf-8")
      );

      if (payload.exp && payload.exp < nowSeconds) {
        expiredCount++;
        const expDate = new Date(payload.exp * 1000).toISOString();
        console.log("Line " + lineCount + ": expired token for " + payload.sub + ", exp=" + expDate);
      }
    } catch {
      // 잘못된 형식의 줄 건너뜀
    }
  }

  console.log(`\n${lineCount}개 줄 스캔 완료, ${expiredCount}개 만료 토큰 발견`);
}

scanLogsForExpiredTokens("./logs/api-access-2026-03.ndjson");
Node.js — 로그 스트림에서 고유한 JWT 주체 추출
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";

async function extractUniqueSubjects(logPath) {
  const rl = createInterface({
    input: createReadStream(logPath, { encoding: "utf-8" }),
    crlfDelay: Infinity,
  });

  const subjects = new Set();
  const jwtRegex = /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;

  for await (const line of rl) {
    const matches = line.match(jwtRegex);
    if (!matches) continue;

    for (const token of matches) {
      try {
        const payload = JSON.parse(
          Buffer.from(token.split(".")[1], "base64url").toString("utf-8")
        );
        if (payload.sub) subjects.add(payload.sub);
      } catch {
        // 유효한 JWT가 아님
      }
    }
  }

  console.log(`${subjects.size}개의 고유 주체 발견:`);
  for (const sub of subjects) console.log(`  ${sub}`);
}

extractUniqueSubjects("./logs/gateway-2026-03.log");
참고:로그 파일이 50 MB를 넘을 때 스트리밍으로 전환하세요. readFileSync로 500 MB NDJSON 파일을 로드하면 메모리를 고정하고 GC 일시 중지를 트리거합니다. readline 방법은 일정한 메모리 사용량으로 한 번에 한 줄씩 처리합니다.

일반적인 실수

ASCII가 아닌 클레임에 TextDecoder 없이 atob() 사용

문제: atob()는 Latin-1 문자열을 반환합니다. 다중 바이트 UTF-8 문자(이모지, 한국어·한자·일본어, 악센트 문자)는 여러 문자로 분리되어 깨져서 나옵니다.

해결: atob() 출력을 Uint8Array로 변환한 다음 new TextDecoder('utf-8')를 통과시키세요.

Before · JavaScript
After · JavaScript
// ASCII가 아닌 페이로드 클레임에서 깨짐
const payload = JSON.parse(atob(token.split(".")[1]));
// display_name이 "김민준" 대신 깨진 문자로 나타남
const binary = atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"));
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
const payload = JSON.parse(new TextDecoder("utf-8").decode(bytes));
// display_name이 "김민준"으로 올바르게 표시됨
base64url에서 base64로의 문자 치환 누락

문제: atob()가 "InvalidCharacterError"를 던집니다. base64url은 + 와 / 대신 - 와 _ 를 사용하기 때문입니다.

해결: atob() 호출 전에 - 를 + 로, _ 를 / 로 치환하세요. Node.js Buffer.from()은 'base64url'로 이를 자동으로 처리합니다.

Before · JavaScript
After · JavaScript
// 던짐: InvalidCharacterError: String contains an invalid character
const payload = atob(token.split(".")[1]);
const segment = token.split(".")[1];
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
const payload = atob(base64); // 이제 작동
서명 검증 없이 디코딩된 JWT 클레임 신뢰

문제: 누구나 원하는 페이로드로 JWT를 만들 수 있습니다. 디코딩은 데이터를 읽기만 할 뿐 — 토큰이 인증 서버에서 발급되었다는 것을 증명하지 않습니다.

해결: 서버 측에서는 항상 jose.jwtVerify() 또는 jsonwebtoken.verify()로 서명을 검증하세요. 디코딩 전용은 사용자 클레임의 클라이언트 측 표시에는 허용됩니다.

Before · JavaScript
After · JavaScript
// 위험: 디코딩되었지만 검증되지 않음
const claims = JSON.parse(atob(token.split(".")[1]));
if (claims.role === "admin") {
  grantAdminAccess(); // 공격자가 이를 위조할 수 있음
}
import * as jose from "jose";
const { payload } = await jose.jwtVerify(token, secretKey);
if (payload.role === "admin") {
  grantAdminAccess(); // 안전 — 서명이 검증됨
}
exp를 1000으로 나누지 않고 Date.now()와 비교

문제: JWT exp는 epoch 이후 초 단위이지만, Date.now()는 밀리초를 반환합니다. 밀리초 타임스탬프가 1000배 더 크기 때문에 비교는 항상 토큰이 유효하다고 말할 것입니다.

해결: exp와 비교하기 전에 Date.now()를 1000으로 나누고 결과를 내림하세요.

Before · JavaScript
After · JavaScript
// 버그: Date.now()는 밀리초, exp는 초 단위
if (payload.exp > Date.now()) {
  console.log("토큰이 유효합니다"); // 항상 true — 잘못됨!
}
const nowSeconds = Math.floor(Date.now() / 1000);
if (payload.exp > nowSeconds) {
  console.log("토큰이 유효합니다"); // 올바른 비교
}

JWT 디코딩 방법 — 빠른 비교

방법
실행 환경
UTF-8 안전
서명 검증
커스텀 타입
설치 필요
atob() + TextDecoder
브라우저
N/A (읽기 전용)
아니오
Buffer.from()
Node.js
N/A (읽기 전용)
아니오
decodeURIComponent()
브라우저 (레거시)
N/A (읽기 전용)
아니오
jose
양쪽 모두
✓ (JWS/JWE)
npm install
jsonwebtoken
Node.js
npm install
jwt-decode
양쪽 모두
N/A
npm install

사용자에게 클레임을 표시하기만 할 때는 브라우저 측 디코딩에 atob() + TextDecoder를 사용하세요. Node.js 스크립트와 CLI 도구에서는 Buffer.from()을 사용하세요. 서버 측 인증 미들웨어인 서명 검증이 필요한 순간에는 jose를 선택하세요. jwt-decode 패키지는 브라우저에서 디코딩 전용을 위한 단일 함수 API를 원할 때 가벼운 대안입니다. 코드를 작성하지 않고 빠른 시각적 검사를 원한다면 토큰을 JWT 디코더 도구에 붙여넣으세요.

자주 묻는 질문

라이브러리 없이 JavaScript에서 JWT 토큰을 디코딩하는 방법은?

토큰을 "."으로 분리하고 두 번째 세그먼트(페이로드)를 가져온 뒤, - 를 + 로, _ 를 / 로 치환하여 base64url 인코딩을 정규화하고, = 문자로 패딩한 다음, atob()를 호출하고 TextDecoder로 UTF-8 JSON 문자열을 얻습니다. 그 결과를 JSON.parse()에 전달하면 클레임 객체가 됩니다. npm 패키지가 필요하지 않습니다. 이 방법은 모든 최신 브라우저와 Node.js 18+에서 작동합니다. 헤더도 읽어야 한다면 첫 번째 세그먼트에도 동일한 디코딩 단계를 적용하세요. 이 방법은 서명 검증 없이 원시 데이터를 제공하므로 서버 측에서 서명을 검증하지 않는 한 결과를 표시 전용으로 취급하세요.

JavaScript
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
const payload = token.split(".")[1];
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
const json = atob(base64);
const claims = JSON.parse(json);
console.log(claims);
// { sub: "usr_921f", role: "admin" }

JWT 디코딩에서 atob()와 Buffer.from()의 차이는 무엇인가요?

atob()는 표준 Base64를 Latin-1 바이너리 문자열로 디코딩하는 브라우저 API입니다. base64url 인코딩을 직접 이해하지 못하므로 - 와 _ 문자를 먼저 치환해야 합니다. Buffer.from(segment, "base64url")은 base64url 알파벳을 네이티브로 처리하고 .toString("utf-8")을 호출할 수 있는 Buffer를 반환하는 Node.js API입니다. 브라우저에서는 atob()를, Node.js에서는 Buffer.from()을 사용하세요. 세 번째 옵션인 decodeURIComponent 퍼센트 인코딩 트릭은 느리지만 역사적으로 많이 사용되었으나, 일부 오래된 코드에서 deprecated된 escape() 함수에 의존하므로 새 코드에서는 피해야 합니다. 브라우저와 서버 양쪽에서 실행되는 동형(isomorphic) 코드의 경우 typeof Buffer !== "undefined"를 확인하여 분기하세요.

JavaScript
// 브라우저
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));

// Node.js
const json2 = Buffer.from(payload, "base64url").toString("utf-8");

atob()가 ASCII가 아닌 JWT 클레임에서 깨지는 이유는?

atob()는 각 문자가 단일 바이트에 매핑되는 Latin-1 문자열을 반환합니다. 다중 바이트 UTF-8 시퀀스(이모지, 한국어·한자·일본어 문자, Latin-1 범위를 넘는 악센트 문자)는 여러 문자로 분리되어 깨진 출력이 됩니다. 해결책은 바이너리 문자열을 먼저 Uint8Array로 변환한 다음 new TextDecoder("utf-8").decode()에 전달하는 것입니다. TextDecoder API는 다중 바이트 시퀀스를 올바르게 재조합합니다. 대부분의 JWT 페이로드에는 ASCII 사용자 ID, 타임스탬프, 역할 이름만 포함되어 있어 개발 중에 이 문제를 놓치기 쉽습니다. 버그는 클레임에 ASCII가 아닌 display_name이나 현지화 문자열이 포함될 때 나타납니다. 현재 페이로드가 ASCII만 포함하더라도 항상 TextDecoder 경로를 사용하세요.

JavaScript
// 잘못됨: atob는 Latin-1 반환, 다중 바이트 문자 깨짐
const broken = atob(base64); // "ð\x9F\x8E\x89" 대신 이모지가 나와야 함

// 올바름: 바이트 배열로 변환 후 TextDecoder 사용
const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
const fixed = new TextDecoder("utf-8").decode(bytes);

JavaScript에서 JWT 서명을 검증할 수 있나요?

디코딩과 검증은 다른 작업입니다. 디코딩은 페이로드를 읽기만 하며 암호화되어 있지 않습니다. 검증은 시크릿(HMAC) 또는 공개 키(RSA/ECDSA)를 통해 서명을 확인합니다. jose 라이브러리는 Web Crypto API를 통해 브라우저와 Node.js 양쪽에서 이를 지원합니다. jsonwebtoken 패키지는 Node.js에서만 작동합니다. 서버 측에서 서명을 검증하지 않고 디코딩된 클레임을 신뢰하지 마세요. 클라이언트 측에서 사용자의 display_name이나 만료 시간을 읽기 위해 JWT를 디코딩하는 것은 허용되지만, 특정 역할이나 권한이 있는지 확인하는 등 모든 접근 제어 결정은 검증 후 서버 측 코드에서 이루어져야 합니다. JWT 형식을 이해하는 공격자는 임의의 클레임으로 토큰을 위조할 수 있으며 클라이언트 측 검사는 통과됩니다.

JavaScript
import * as jose from "jose";

const secret = new TextEncoder().encode("your-256-bit-secret");
const { payload } = await jose.jwtVerify(token, secret);
console.log(payload.sub); // 검증된 클레임

JavaScript에서 JWT가 만료되었는지 확인하는 방법은?

페이로드를 디코딩하고 초 단위 Unix 타임스탬프인 exp 클레임을 읽습니다. Math.floor(Date.now() / 1000)을 사용하여 현재 시간과 비교합니다. 현재 시간이 exp보다 크면 토큰이 만료된 것입니다. exp 값은 밀리초가 아닌 epoch 이후 초 단위이므로 Date.now()를 1000으로 나누는 것이 필요합니다. 실제로는 작은 클럭 스큐 버퍼를 두는 것이 좋습니다. 정확히 만료되었는지가 아니라 다음 30초 이내에 만료될지 확인하면, 디코딩할 때는 유효하지만 다음 다운스트림 API 호출이 처리될 때쯤 만료되는 엣지 케이스를 방지할 수 있습니다. exp가 완전히 없는 경우도 처리해야 하며, 이는 토큰이 절대 만료되지 않음을 의미합니다.

JavaScript
function isTokenExpired(token) {
  const payload = JSON.parse(
    atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))
  );
  const nowSeconds = Math.floor(Date.now() / 1000);
  return payload.exp < nowSeconds;
}

console.log(isTokenExpired(myToken)); // true 또는 false

Node.js와 브라우저 양쪽에서 작동하는 동형 JWT 디코딩 코드를 작성하는 방법은?

globalThis.Buffer의 존재 여부를 확인하세요. 존재하면 Node.js 환경이므로 Buffer.from(segment, "base64url").toString("utf-8")을 사용할 수 있습니다. 존재하지 않으면 브라우저 환경이므로 atob()와 TextDecoder 방법을 사용해야 합니다. 이 검사를 단일 decodeBase64Url 함수로 감싸서 어디서나 사용하세요. 이는 Next.js 서버 컴포넌트와 브라우저 React 컴포넌트 양쪽에서 임포트되는 모노레포 패키지의 공유 코드, 유틸리티 패키지, 디자인 시스템 컴포넌트에 가장 중요합니다. 환경 감지를 한 곳에 유지하면 런타임이 변경될 때 한 곳만 업데이트하면 됩니다.

JavaScript
function decodeBase64Url(segment) {
  if (typeof Buffer !== "undefined") {
    return Buffer.from(segment, "base64url").toString("utf-8");
  }
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
  return new TextDecoder("utf-8").decode(bytes);
}

관련 도구

다른 언어로도 제공됩니다:Python
MW
Marcus WebbJavaScript Performance Engineer

Marcus specialises in JavaScript performance, build tooling, and the inner workings of the V8 engine. He has spent years profiling and optimising React applications, working on bundler configurations, and squeezing every millisecond out of critical rendering paths. He writes about Core Web Vitals, JavaScript memory management, and the tools developers reach for when performance really matters.

SL
Sophie Laurent기술 검토자

Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.