JavaScript Base64 디코딩 완전 가이드 — atob() vs Buffer
무료 Base64 디코더을 브라우저에서 직접 사용하세요 — 설치 불필요.
Base64 디코더 온라인으로 사용하기 →프로덕션 인증 이슈를 디버깅할 때 제가 가장 먼저 찾는 것은 Base64 디코더입니다 — JWT 페이로드, 웹훅 서명, 인코딩된 설정 값이 모두 Base64 문자열 안에 숨어 있습니다. JavaScript는 Base64 디코딩을 위한 두 가지 주요 내장 방식을 제공합니다: atob()(브라우저 + Node.js 16+)와 Buffer.from(encoded, 'base64').toString() (Node.js) — 원본 데이터에 유니코드 문자가 포함된 경우 두 방식의 동작이 크게 다릅니다. 코드 작성 없이 빠르게 디코딩하려면, ToolDeck's Base64 Decoder 가 브라우저에서 즉시 처리해 드립니다. 이 가이드는 두 환경 모두를 다룹니다 — Node.js 16+와 최신 브라우저(Chrome 80+, Firefox 75+, Safari 14+)를 대상으로 — 프로덕션에서 바로 쓸 수 있는 예제를 담고 있습니다: UTF-8 복원, URL 안전 변형, JWT 디코딩, 파일, API 응답, Node.js 스트림, 그리고 실제 코드베이스에서 반복적으로 깨진 출력을 만들어내는 네 가지 실수들입니다.
- ✓atob(encoded)는 브라우저 네이티브이며 Node.js 16+에서도 전역으로 사용 가능하지만, 바이너리 문자열을 반환합니다 — ASCII를 초과하는 모든 콘텐츠에서 UTF-8 텍스트를 복원하려면 TextDecoder를 사용하세요.
- ✓Buffer.from(encoded, "base64").toString("utf8")은 Node.js의 관용적인 접근 방식으로 추가 단계 없이 UTF-8을 자동으로 처리합니다.
- ✓URL 안전 Base64(JWT에서 사용)는 +를 -로, /를 _로 바꾸고 = 패딩을 제거합니다. atob()를 호출하기 전에 이를 복원하거나 Node.js 18+에서 Buffer.from(encoded, "base64url").toString()을 사용하세요.
- ✓디코딩 전에 공백과 개행을 제거하세요 — GitHub Contents API와 많은 MIME 인코더는 Base64 출력을 줄당 60~76자로 줄 바꿈합니다.
- ✓Uint8Array.prototype.fromBase64()(TC39 Stage 3)는 이미 Node.js 22+와 Chrome 130+에서 사용 가능하며, 결국 두 환경을 통합할 것입니다.
Base64 디코딩이란?
Base64 디코딩은 인코딩의 역과정입니다 — 64개 문자 ASCII 표현을 원래의 바이너리 데이터나 텍스트로 되돌립니다. 4개의 Base64 문자는 정확히 3바이트로 다시 매핑됩니다. 인코딩된 문자열 끝의 = 패딩 문자는 마지막 3바이트 그룹을 완성하기 위해 몇 바이트가 추가되었는지를 디코더에 알려줍니다.
Base64는 암호화가 아닙니다 — 인코딩된 문자열을 가진 누구든지 완전히 역산할 수 있습니다. 그 목적은 전송 안전성입니다: 7비트 ASCII 텍스트용으로 설계된 프로토콜과 저장 형식은 임의의 바이너리 바이트를 처리할 수 없으며, Base64가 그 간극을 메웁니다. JavaScript에서 흔한 디코딩 시나리오로는 JWT 페이로드 검사, 환경 변수에서 Base64 인코딩된 JSON 설정 해제, REST API에서 바이너리 파일 콘텐츠 추출, 브라우저에서 data URI 디코딩 등이 있습니다.
ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
deploy-bot:sk-prod-a7f2c91e4b3d8
atob() — 브라우저 네이티브 디코딩 함수
atob()(ASCII-to-binary)는 IE10부터 브라우저에서 사용 가능했으며 WinterCG 호환성 이니셔티브의 일환으로 Node.js 16.0에서 전역 함수가 되었습니다. Deno, Bun, Cloudflare Workers에서도 네이티브로 동작하며 import가 필요 없습니다.
이 함수는 바이너리 문자열을 반환합니다: 각 문자의 코드 포인트가 하나의 원시 바이트 값(0–255)과 같은 JavaScript 문자열입니다. 이것이 중요한 이유는: 원본 데이터가 U+007F 이상의 문자 (악센트 문자, 키릴 문자, CJK, 이모지)를 포함한 UTF-8 텍스트였다면, 반환된 문자열은 읽을 수 있는 텍스트가 아닌 원시 바이트 시퀀스입니다. 복원하려면 TextDecoder를 사용하세요(다음 섹션에서 다룹니다).
최소 동작 예제
// Decoding an HTTP Basic Auth credential pair received in a request header
// Authorization: Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
function parseBasicAuth(header: string): { serviceId: string; apiKey: string } {
const base64Part = header.replace(/^Basics+/i, '')
const decoded = atob(base64Part)
const [serviceId, apiKey] = decoded.split(':')
return { serviceId, apiKey }
}
const auth = parseBasicAuth('Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=')
console.log(auth.serviceId) // deploy-bot
console.log(auth.apiKey) // sk-prod-a7f2c91e4b3d8왕복 검증
// Verify lossless recovery for ASCII-only content const original = 'service:payments region:eu-west-1 env:production' const encoded = btoa(original) const decoded = atob(encoded) console.log(encoded) // c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u console.log(decoded === original) // true
atob()와 btoa()는 WinterCG Minimum Common API 의 일부입니다 — 비브라우저 런타임에서 Fetch, URL, crypto를 관장하는 동일한 사양입니다. Node.js 16+, Bun, Deno, Cloudflare Workers에서 동일하게 동작합니다.디코딩 후 UTF-8 텍스트 복원
atob()의 가장 흔한 함정은 반환 타입을 잘못 이해하는 것입니다. 원본 텍스트가 Base64로 인코딩되기 전에 UTF-8로 인코딩되었다면,atob()는 읽을 수 있는 텍스트가 아닌 Latin-1 바이너리 문자열을 반환합니다:
// '김민준' was UTF-8 encoded then Base64 encoded before transmission const encoded = '6rmA66mU7KSA' // ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string — garbled output console.log(atob(encoded)) // "김민ì€" ← byte values misread as Latin-1
올바른 접근 방식은 TextDecoder를 사용하여 원시 바이트를 UTF-8로 해석하는 것입니다:
TextDecoder 접근 방식 — 모든 유니코드 출력에 안전
// Unicode-safe Base64 decode utilities
function fromBase64(encoded: string): string {
const binary = atob(encoded)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
function toBase64(text: string): string {
const bytes = new TextEncoder().encode(text)
const chars = Array.from(bytes, byte => String.fromCharCode(byte))
return btoa(chars.join(''))
}
// Works with any language or script
const orderNote = '확인됨: 김민준 — 서울 창고, 수량: 250'
const encoded = toBase64(orderNote)
const decoded = fromBase64(encoded)
console.log(decoded === orderNote) // true
console.log(decoded)
// 확인됨: 김민준 — 서울 창고, 수량: 250Buffer.from(encoded, 'base64').toString('utf8')를 사용하세요. 디코딩된 바이트를 자동으로 UTF-8로 해석하며 대용량 입력에서 더 빠릅니다.Node.js의 Buffer.from() — 완전한 디코딩 가이드
Node.js에서 Buffer는 Base64 디코딩을 포함한 모든 바이너리 작업을 위한 관용적인 API입니다. UTF-8을 네이티브로 처리하고, 진정한 Buffer(바이너리 안전)를 반환하며, Node.js 18부터 URL 안전 변형을 위한 'base64url' 인코딩 단축키를 지원합니다.
환경 변수 설정 디코딩
// Server config stored as Base64 in an env variable (avoids JSON escaping in shell)
// DB_CONFIG=eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5hbHl0aWNzX3Byb2QiLCJtYXhDb25uZWN0aW9ucyI6MTAwfQ==
const raw = Buffer.from(process.env.DB_CONFIG!, 'base64').toString('utf8')
const dbConfig = JSON.parse(raw)
console.log(dbConfig.host) // db-primary.internal
console.log(dbConfig.port) // 5432
console.log(dbConfig.maxConnections) // 100.b64 파일에서 바이너리 파일 복원
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
// Read the Base64-encoded certificate and restore the original binary
const encoded = readFileSync(join(process.cwd(), 'dist', 'cert.b64'), 'utf8').trim()
const certBuf = Buffer.from(encoded, 'base64')
writeFileSync('./ssl/server.crt', certBuf)
console.log(`Restored ${certBuf.length} bytes`)
// Restored 2142 bytes오류 처리가 포함된 비동기 디코딩
import { readFile, writeFile } from 'node:fs/promises'
async function decodeBase64File(
encodedPath: string,
outputPath: string,
): Promise<number> {
try {
const encoded = await readFile(encodedPath, 'utf8')
const binary = Buffer.from(encoded.trim(), 'base64')
await writeFile(outputPath, binary)
return binary.length
} catch (err) {
const code = (err as NodeJS.ErrnoException).code
if (code === 'ENOENT') throw new Error(`File not found: ${encodedPath}`)
if (code === 'EACCES') throw new Error(`Permission denied: ${encodedPath}`)
throw err
}
}
// Restore a PDF stored as Base64
const bytes = await decodeBase64File('./uploads/invoice.b64', './out/invoice.pdf')
console.log(`Decoded ${bytes} bytes — PDF restored`)Base64 디코딩 함수 — 파라미터 레퍼런스
코드를 작성하거나 리뷰할 때 참조할 수 있는 두 가지 주요 네이티브 디코딩 API 파라미터 빠른 레퍼런스입니다.
atob(encodedData)
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| encodedData | string | 예 | +, /, = 문자를 사용하는 표준 Base64 문자열. URL 안전 변형(-, _)은 InvalidCharacterError를 던집니다. 공백은 허용되지 않습니다. |
Buffer.from(input, inputEncoding) / .toString(outputEncoding)
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| input | string | Buffer | TypedArray | ArrayBuffer | 필수 | 디코딩할 Base64 인코딩된 문자열, 또는 인코딩된 바이트를 포함하는 Buffer. |
| inputEncoding | BufferEncoding | "utf8" | 표준 Base64(RFC 4648 §4)에는 "base64", URL 안전 Base64(RFC 4648 §5, Node.js 18+)에는 "base64url"로 설정. |
| outputEncoding | string | "utf8" | .toString() 출력의 인코딩. 읽을 수 있는 텍스트에는 "utf8", atob() 출력과 호환되는 Latin-1 바이너리 문자열에는 "binary" 사용. |
| start | integer | 0 | 읽기 시작할 디코딩된 Buffer 내의 바이트 오프셋. .toString()의 두 번째 인수로 전달됨. |
| end | integer | buf.length | 읽기를 중지할 바이트 오프셋(제외). .toString()의 세 번째 인수로 전달됨. |
URL 안전 Base64 — JWT와 URL 파라미터 디코딩
JWT는 세 개의 세그먼트 모두에 URL 안전 Base64(RFC 4648 §5)를 사용합니다. URL 안전 Base64는 +를 -로, /를 _로 바꾸고, 말미의 = 패딩을 제거합니다. 복원 없이 이것을 atob()에 전달하면 잘못된 출력이 나오거나 예외가 발생합니다.
브라우저 — 디코딩 전 문자와 패딩 복원
function decodeBase64Url(input: string): string {
const base64 = input.replace(/-/g, '+').replace(/_/g, '/')
const padded = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
const binary = atob(padded)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
// Inspect a JWT payload segment (the middle part between the two dots)
const jwtToken = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9'
const payload = decodeBase64Url(jwtToken)
const claims = JSON.parse(payload)
console.log(claims.userId) // usr_9f2a1c3e8b4d
console.log(claims.role) // editor
console.log(claims.workspaceId) // ws_3a7f91c2Node.js 18+ — 네이티브 'base64url' 인코딩
// Node.js 18 added 'base64url' as a first-class Buffer encoding — no manual replace needed
function decodeJwtSegment(segment: string): Record<string, unknown> {
const json = Buffer.from(segment, 'base64url').toString('utf8')
return JSON.parse(json)
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9.SIGNATURE'
const [headerB64, payloadB64] = token.split('.')
const header = decodeJwtSegment(headerB64)
const payload = decodeJwtSegment(payloadB64)
console.log(header.alg) // HS256
console.log(payload.role) // editor
console.log(payload.workspaceId) // ws_3a7f91c2파일과 API 응답에서 Base64 디코딩
프로덕션 코드에서 Base64 디코딩은 인코딩된 형태로 콘텐츠를 전달하는 외부 API를 소비할 때 가장 자주 발생합니다. 두 시나리오 모두 공백 처리와 바이너리 vs 텍스트 출력에 관한 중요한 주의 사항이 있습니다. 디버깅 중에 인코딩된 응답을 확인하기만 하려면, 직접 Base64 Decoder 에 붙여넣으세요 — 표준과 URL 안전 모드를 즉시 처리합니다.
GitHub Contents API에서 콘텐츠 디코딩
// GitHub Contents API returns file content as Base64, wrapped at 60 chars per line
async function fetchDecodedFile(
owner: string,
repo: string,
path: string,
token: string,
): Promise<string> {
const res = await fetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
{ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github.v3+json' } }
)
if (!res.ok) throw new Error(`GitHub API ${res.status}: ${res.statusText}`)
const data = await res.json() as { content: string; encoding: string }
if (data.encoding !== 'base64') throw new Error(`Unexpected encoding: ${data.encoding}`)
// ⚠️ GitHub wraps at 60 chars — strip newlines before decoding
const clean = data.content.replace(/\n/g, '')
return Buffer.from(clean, 'base64').toString('utf8')
}
const openApiSpec = await fetchDecodedFile('acme-corp', 'platform-api', 'openapi.json', process.env.GITHUB_TOKEN!)
const spec = JSON.parse(openApiSpec)
console.log(`API version: ${spec.info.version}`)API에서 Base64 인코딩된 바이너리 디코딩(브라우저)
// Some APIs return binary content (images, PDFs) as Base64 JSON fields
async function downloadDecodedFile(endpoint: string, authToken: string): Promise<void> {
const res = await fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
if (!res.ok) throw new Error(`Download failed: ${res.status}`)
const { filename, content, mimeType } = await res.json() as {
filename: string; content: string; mimeType: string
}
// Decode Base64 → binary bytes → Blob
const binary = atob(content)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
const blob = new Blob([bytes], { type: mimeType })
// Trigger browser download
const url = URL.createObjectURL(blob)
const a = Object.assign(document.createElement('a'), { href: url, download: filename })
a.click()
URL.revokeObjectURL(url)
}
await downloadDecodedFile('/api/reports/latest', sessionStorage.getItem('auth_token')!)Node.js와 셸에서 커맨드라인 Base64 디코딩
CI/CD 스크립트, 디버깅 세션, 또는 일회성 디코딩 작업에는 셸 도구와 Node.js 원라이너가 완전한 스크립트보다 빠릅니다. macOS와 Linux에서 플래그 이름이 다르다는 점을 주의하세요.
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard decoding (macOS uses -D, Linux uses -d)
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -d # Linux
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -D # macOS
# Decode a .b64 file to its original binary
base64 -d ./dist/cert.b64 > ./ssl/server.crt # Linux
base64 -D -i ./dist/cert.b64 -o ./ssl/server.crt # macOS
# URL-safe Base64 — restore + and / before decoding
echo "eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ" | tr '-_' '+/' | base64 -d
# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
# deploy-bot
# URL-safe (Node.js 18+)
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64url').toString())" "eyJhbGciOiJIUzI1NiJ9"
# {"alg":"HS256"}base64는 디코딩에 -D(대문자)를 사용하지만, Linux는 -d(소문자)를 사용합니다. 이로 인해 CI 스크립트가 조용히 실패합니다 — 대상 플랫폼이 Linux임이 보장되지 않는 경우 Node.js 원라이너를 사용하세요.고성능 대안: js-base64
라이브러리를 선택하는 주된 이유는 크로스 환경 일관성입니다. 번들러 설정 없이 브라우저와 Node.js 모두에서 실행되는 패키지를 배포한다면,Buffer는 환경 감지가 필요하고atob()는 TextDecoder 우회 방법이 필요합니다.js-base64(npm 주간 다운로드 1억 이상)는 두 경우를 투명하게 처리합니다.
npm install js-base64 # or pnpm add js-base64
import { fromBase64, fromBase64Url, isValid } from 'js-base64'
// Standard decoding — Unicode-safe, works in browser and Node.js
const raw = fromBase64('eyJldmVudElkIjoiZXZ0XzdjM2E5ZjFiMmQiLCJ0eXBlIjoiY2hlY2tvdXRfY29tcGxldGVkIiwiY3VycmVuY3kiOiJFVVIiLCJhbW91bnQiOjE0OTAwfQ==')
const event = JSON.parse(raw)
console.log(event.type) // checkout_completed
console.log(event.currency) // EUR
// URL-safe decoding — no manual character replacement needed
const jwtPayload = fromBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciJ9')
const claims = JSON.parse(jwtPayload)
console.log(claims.role) // editor
// Validate before decoding untrusted input
const untrusted = 'not!valid@base64#'
if (!isValid(untrusted)) {
console.error('Rejected: invalid Base64 input')
}
// Binary output — second argument true returns Uint8Array
const pngBytes = fromBase64('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', true)
console.log(pngBytes instanceof Uint8Array) // true구문 강조가 포함된 터미널 출력
CLI 디버깅 도구나 검사 스크립트를 작성할 때, 대용량 JSON 페이로드에 대해 일반 console.log 출력은 읽기 어렵습니다.chalk(터미널 색상화를 위한 가장 많이 다운로드된 npm 패키지)와 Base64 디코딩을 결합하면 읽기 쉽고 스캔하기 좋은 터미널 출력을 만들 수 있습니다 — JWT 검사, API 응답 디버깅, 설정 감사에 유용합니다.
npm install chalk # chalk v5+ is ESM-only — use import, not require
import chalk from 'chalk'
// Decode and display any Base64 value with smart type detection
function inspectBase64(encoded: string, label = 'Decoded value'): void {
let decoded: string
try {
decoded = Buffer.from(encoded.trim(), 'base64').toString('utf8')
} catch {
console.error(chalk.red('✗ Invalid Base64 input'))
return
}
console.log(chalk.bold.cyan(`\n── ${label} ──`))
// Attempt JSON pretty-print
try {
const parsed = JSON.parse(decoded)
console.log(chalk.green('Type:'), chalk.yellow('JSON'))
for (const [key, value] of Object.entries(parsed)) {
const display = typeof value === 'object' ? JSON.stringify(value) : String(value)
console.log(chalk.green(` ${key}:`), chalk.white(display))
}
return
} catch { /* not JSON */ }
// Plain text fallback
console.log(chalk.green('Type:'), chalk.yellow('text'))
console.log(chalk.white(decoded))
}
// Inspect a Base64-encoded JWT payload
const tokenParts = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsImV4cCI6MTcxNzIwMzYwMH0.SIGNATURE'.split('.')
inspectBase64(tokenParts[0], 'JWT Header')
inspectBase64(tokenParts[1], 'JWT Payload')
// ── JWT Header ──
// Type: JSON
// alg: HS256
// typ: JWT
//
// ── JWT Payload ──
// Type: JSON
// userId: usr_9f2a1c3e8b4d
// role: editor
// exp: 1717203600Node.js 스트림을 활용한 대용량 Base64 파일 디코딩
Base64 인코딩된 파일이 약 50 MB를 초과하면, readFileSync()로 전체를 메모리에 로드하는 것이 문제가 됩니다. Node.js 스트림을 사용하면 청크 단위로 데이터를 디코딩할 수 있습니다 — 하지만 Base64는 청크 경계에서 패딩 오류를 피하기 위해 청크당 4개 문자의 배수가 필요합니다(각 4문자 그룹이 정확히 3바이트로 디코딩됩니다).
import { createReadStream, createWriteStream } from 'node:fs'
import { pipeline } from 'node:stream/promises'
async function streamDecodeBase64(inputPath: string, outputPath: string): Promise<void> {
const readStream = createReadStream(inputPath, { encoding: 'utf8', highWaterMark: 4 * 1024 * 192 })
const writeStream = createWriteStream(outputPath)
let buffer = ''
await pipeline(
readStream,
async function* (source) {
for await (const chunk of source) {
buffer += (chunk as string).replace(/\s/g, '') // strip any whitespace/newlines
// Decode only complete 4-char groups to avoid mid-stream padding issues
const remainder = buffer.length % 4
const safe = buffer.slice(0, buffer.length - remainder)
buffer = buffer.slice(buffer.length - remainder)
if (safe.length > 0) yield Buffer.from(safe, 'base64')
}
if (buffer.length > 0) yield Buffer.from(buffer, 'base64')
},
writeStream,
)
}
// Decode a 200 MB video that was stored as Base64
await streamDecodeBase64('./uploads/product-demo.b64', './dist/product-demo.mp4')
console.log('Stream decode complete')4 × 1024 × 192 = 786,432자(768 KB)를 사용합니다. 50 MB 미만의 파일에는 readFile() + Buffer.from(content.trim(), 'base64')가 더 단순하고 충분히 빠릅니다.흔한 실수들
저는 이 네 가지 실수를 JavaScript 코드베이스에서 반복적으로 봤습니다 — 비 ASCII 문자나 줄 바꿈된 API 응답이 프로덕션의 디코딩 경로에 도달할 때까지 숨어 있는 경향이 있습니다.
실수 1 — UTF-8 콘텐츠에 TextDecoder 없이 atob() 사용
문제: atob()는 각 문자가 하나의 원시 바이트 값을 나타내는 바이너리 문자열을 반환합니다. UTF-8 멀티바이트 시퀀스(키릴 문자, CJK, 악센트 문자)가 깨진 Latin-1 문자로 나타납니다. 수정: 출력을 TextDecoder로 감싸세요.
// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' const decoded = atob(encoded) console.log(decoded) // "Алексей Р˜РІР°РЅРѕРІ" ← wrong
// ✅ Use TextDecoder to correctly interpret the UTF-8 bytes const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' const binary = atob(encoded) const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0)) const decoded = new TextDecoder().decode(bytes) console.log(decoded) // Алексей Иванов ✓
실수 2 — URL 안전 Base64를 직접 atob()에 전달
문제: JWT 세그먼트는 +와 / 대신 -와 _를 사용하며 패딩이 없습니다.atob()는 잘못된 결과를 반환하거나 예외를 던질 수 있습니다. 수정: 먼저 표준 문자를 복원하고 패딩을 추가하세요.
// ❌ URL-safe JWT segment passed directly — unreliable const jwtPayload = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ' const decoded = atob(jwtPayload) // May produce wrong result or throw
// ✅ Restore standard Base64 chars and padding first
function decodeBase64Url(input: string): string {
const b64 = input.replace(/-/g, '+').replace(/_/g, '/')
const pad = b64 + '==='.slice(0, (4 - b64.length % 4) % 4)
const bin = atob(pad)
const bytes = Uint8Array.from(bin, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"} ✓실수 3 — 줄 바꿈된 Base64에서 개행 제거 안 함
문제: GitHub Contents API와 MIME 인코더는 Base64 출력을 줄당 60~76자로 줄 바꿈합니다.atob()는 \n 문자에서 InvalidCharacterError를 던집니다. 수정: 디코딩 전에 모든 공백을 제거하세요.
// ❌ GitHub API content field contains embedded newlines const data = await res.json() const decoded = atob(data.content) // ❌ throws InvalidCharacterError
// ✅ Strip newlines (and any other whitespace) before decoding const data = await res.json() const clean = data.content.replace(/\s/g, '') const decoded = atob(clean) // ✓
실수 4 — 디코딩된 바이너리 콘텐츠에 .toString() 호출
문제: 원본 데이터가 바이너리(이미지, PDF, 오디오)인 경우, .toString('utf8')를 호출하면 인식할 수 없는 바이트 시퀀스를 U+FFFD로 교체하여 출력을 조용히 손상시킵니다. 수정: 결과를 Buffer로 유지하세요 — 문자열로 변환하지 마세요.
// ❌ .toString('utf8') corrupts binary content
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const corrupted = Buffer.from(encoded, 'base64').toString('utf8') // ❌
writeFileSync('./out/invoice.pdf', corrupted) // ❌ unreadable PDF// ✅ Keep the Buffer as binary — do not convert to a string
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const binary = Buffer.from(encoded, 'base64') // ✓ raw bytes preserved
writeFileSync('./out/invoice.pdf', binary) // ✓ valid PDFJavaScript Base64 디코딩 방법 — 빠른 비교
| 방법 | UTF-8 출력 | 바이너리 출력 | URL 안전 | 환경 | 설치 필요 |
|---|---|---|---|---|---|
| atob() | ❌ TextDecoder 필요 | ✅ 바이너리 문자열 | ❌ 수동 복원 | Browser, Node 16+, Bun, Deno | 아니요 |
| TextDecoder + atob() | ✅ UTF-8 | ✅ Uint8Array 경유 | ❌ 수동 복원 | Browser, Node 16+, Deno | 아니요 |
| Buffer.from().toString() | ✅ utf8 | ✅ Buffer로 유지 | ✅ base64url (Node 18+) | Node.js, Bun | 아니요 |
| Uint8Array.fromBase64() (TC39) | ✅ TextDecoder 경유 | ✅ 네이티브 | ✅ alphabet 옵션 | Chrome 130+, Node 22+ | 아니요 |
| js-base64 | ✅ 항상 | ✅ Uint8Array | ✅ 내장 | 범용 | npm install |
디코딩된 콘텐츠가 ASCII 텍스트임이 보장될 때만 atob()를 선택하세요. 브라우저에서 사용자 입력이나 다국어 텍스트에는 TextDecoder + atob()를 사용하세요. Node.js 서버 사이드 코드에는 Buffer가 올바른 기본값입니다 — UTF-8을 자동으로 처리하고 바이너리 데이터를 그대로 유지합니다. 크로스 환경 라이브러리에는 js-base64가 모든 엣지 케이스를 제거합니다.
자주 묻는 질문
관련 도구
코드 작성 없이 원클릭 디코딩을 위해 Base64 문자열을 직접 Base64 Decoder 에 붙여넣으세요 — 브라우저에서 표준과 URL 안전 모드를 즉시 처리합니다.
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
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.