JavaScript Base64 인코딩 가이드:btoa() vs Buffer 완전 정복

·Front-end & Node.js Developer·검토자Sophie Laurent·게시일

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

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

CSS data URI에 이미지를 삽입하거나, HTTP Authorization 헤더에 자격 증명을 전달하거나, 바이너리 인증서를 환경 변수에 저장할 때, 브라우저와 Node.js 모두에서 JavaScript 데이터를 안정적으로 Base64 인코딩해야 합니다. JavaScript는 두 가지 서로 다른 내장 API를 제공합니다:btoa()는 브라우저 환경용(Node.js 16+에서도 사용 가능),Buffer.from()은 Node.js용입니다 — 각각 Unicode, 바이너리 데이터, URL 안전성에 대해 서로 다른 제약이 있습니다. 코드 작성 없이 빠르게 인코딩하려면, ToolDeck's Base64 Encoder 가 브라우저에서 즉시 처리합니다. 이 가이드는 두 환경의 프로덕션 예제를 모두 다룹니다:Unicode 처리, URL 안전 변형, 파일 및 API 응답 인코딩, CLI 사용법, 그리고 실제 코드베이스에서 반복적으로 발생하는 4가지 실수입니다.

  • btoa()는 브라우저 내장 함수이며 Node.js 16+에서도 전역으로 사용 가능하지만, Latin-1(코드 포인트 0–255)만 허용합니다 — Unicode 입력은 DOMException을 발생시킵니다
  • Buffer.from(text, "utf8").toString("base64")는 Node.js의 동등한 메서드로, 추가 처리 없이 Unicode를 네이티브로 처리합니다
  • URL 안전 Base64는 +를 -로, /를 _로 대체하고 = 패딩을 제거합니다 — Node.js 18+에서 Buffer.from().toString("base64url")로 한 줄에 처리할 수 있습니다
  • 바이너리 데이터(ArrayBuffer, Uint8Array, 파일)에는 Node.js에서 Buffer를, 브라우저에서는 arrayBuffer() + Uint8Array 방식을 사용하세요 — response.text()는 절대 사용하지 마세요
  • Uint8Array.prototype.toBase64()(TC39 Stage 3)는 이미 Node.js 22+와 Chrome 130+에서 사용 가능하며 두 환경을 통합할 예정입니다

Base64 인코딩이란?

Base64는 임의의 바이너리 데이터를 64개의 인쇄 가능한 ASCII 문자로 구성된 문자열로 변환합니다: A–Z, a–z, 0–9, +, 그리고 /. 입력의 3바이트마다 정확히 4개의 Base64 문자로 매핑됩니다. 입력 길이가 3의 배수가 아닌 경우, 1개 또는 2개의 = 패딩 문자가 추가됩니다. 인코딩된 출력은 원본보다 약 33% 더 큽니다.

Base64는 암호화가 아닙니다 — 기밀성을 제공하지 않습니다. 인코딩된 문자열을 가진 누구든 함수 호출 한 번으로 디코딩할 수 있습니다. 그 목적은 전송 안전성입니다:많은 프로토콜과 저장 형식은 7비트 ASCII 텍스트를 위해 설계되어 임의의 바이너리 바이트를 처리할 수 없습니다. Base64가 그 간격을 메웁니다. 일반적인 JavaScript 사용 사례로는 자산 인라인용 data URI, HTTP Basic Auth 헤더, JWT 토큰 세그먼트, 이메일 MIME 첨부 파일, JSON API의 바이너리 blob 저장 등이 있습니다.

Before · text
After · text
deploy-bot:sk-prod-a7f2c91e4b3d8
ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

btoa() — 브라우저 내장 인코딩 함수

btoa()(binary-to-ASCII)는 IE10부터 브라우저에서 사용 가능하며, WinterCG 호환성 이니셔티브의 일환으로 Node.js 16.0에서 전역 함수가 되었습니다. Deno, Bun, Cloudflare Workers에서도 네이티브로 작동합니다. 임포트가 필요 없습니다.

이 함수는 문자열 인수를 하나 받아 Base64 인코딩된 형식을 반환합니다. 대칭적인 역함수 atob()(ASCII-to-binary)로 디코딩합니다. 둘 다 동기적으로 실행되며 입력 크기에 대해 일정한 메모리를 사용합니다.

최소 동작 예제

JavaScript (browser / Node.js 16+)
// Encoding an API credential pair for an HTTP Basic Auth header
const serviceId  = 'deploy-bot'
const apiKey     = 'sk-prod-a7f2c91e4b3d8'

const credential = btoa(`${serviceId}:${apiKey}`)
// → 'ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg='

const headers = new Headers({
  Authorization: `Basic ${credential}`,
  'Content-Type': 'application/json',
})

console.log(headers.get('Authorization'))
// Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

atob()로 디코딩

JavaScript
// Round-trip: encode, transmit, decode
const payload = 'service:payments region:eu-west-1 env:production'

const encoded = btoa(payload)
const decoded = atob(encoded)

console.log(encoded)
// c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u

console.log(decoded === payload) // true
참고:btoa()atob() WinterCG Minimum Common API 의 일부입니다 — 비브라우저 런타임에서 Fetch, URL, crypto를 관리하는 동일한 사양입니다. Node.js 16+, Bun, Deno, Cloudflare Workers에서 동일하게 동작합니다.

Unicode 및 비 ASCII 문자 처리

btoa()의 가장 일반적인 함정은 엄격한 Latin-1 경계입니다. U+00FF를 초과하는 코드 포인트를 가진 모든 문자는 즉시 예외를 발생시킵니다:

JavaScript
btoa('Müller & Søren') // ❌ Uncaught DOMException: String contains an invalid character
btoa('résumé')         // ❌ 'é' is U+00E9 = 233 — within Latin-1, this one actually works
btoa('田中太郎')         // ❌ Throws — all CJK characters are above U+00FF

올바른 방법은 먼저 문자열을 UTF-8 바이트로 인코딩한 다음 해당 바이트를 Base64 인코딩하는 것입니다. JavaScript는 바로 이 목적을 위해 TextEncoder를 제공합니다:

TextEncoder 방식 — 모든 Unicode 입력에 안전

JavaScript (browser + Node.js 16+)
// Utility functions for Unicode-safe Base64
function toBase64(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, byte => String.fromCharCode(byte))
  return btoa(chars.join(''))
}

function fromBase64(encoded: string): string {
  const binary = atob(encoded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

// Works with any language or script
const orderNote = '확인됨:김민준 — 서울 창고, 수량:250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(encoded)
// 7ZmU7J2067KE7J20OuqwgO2Vmesp7J207IaM7J2067KE64qU7IiY6rCA7IqkOiAyNTA=

console.log(decoded === orderNote) // true
참고:이미 Node.js를 사용하고 있다면 TextEncoder 우회 방법을 완전히 건너뛰세요 — Buffer.from(text, 'utf8').toString('base64')를 사용하세요. Unicode를 네이티브로 처리하며 대용량 문자열에서 더 빠릅니다.

Node.js의 Buffer.from() — 예제를 포함한 완전 가이드

Node.js에서 Buffer는 인코딩 변환을 포함한 모든 바이너리 데이터 작업의 관용적인 API입니다. TextEncoder보다 수년 앞서 등장했으며 서버 사이드 코드의 선호 수단으로 남아 있습니다. btoa()에 비한 주요 장점:네이티브 UTF-8 지원, 바이너리 데이터 처리, Node.js 18부터 사용 가능한 'base64url' 인코딩 단축키.

기본 텍스트 인코딩 및 디코딩

Node.js
// Encoding a server configuration object for storage in an env variable
const dbConfig = JSON.stringify({
  host:           'db-primary.internal',
  port:           5432,
  database:       'analytics_prod',
  maxConnections: 100,
  ssl:            { rejectUnauthorized: true },
})

const encoded = Buffer.from(dbConfig, 'utf8').toString('base64')
console.log(encoded)
// eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5h...

// Decoding back
const decoded = Buffer.from(encoded, 'base64').toString('utf8')
const config  = JSON.parse(decoded)

console.log(config.host)           // db-primary.internal
console.log(config.maxConnections) // 100

디스크에서 바이너리 파일 인코딩

Node.js
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'

// Read a TLS certificate and encode it for embedding in a config file
const certPem     = readFileSync(join(process.cwd(), 'ssl', 'server.crt'))
const certBase64  = certPem.toString('base64')

// Store as a single-line string — suitable for env vars or JSON configs
writeFileSync('./dist/cert.b64', certBase64, 'utf8')

console.log(`Certificate encoded: ${certBase64.length} characters`)
// Certificate encoded: 2856 characters

// Restore the binary cert from the encoded value
const restored = Buffer.from(certBase64, 'base64')
console.log(restored.equals(certPem)) // true

오류 처리를 포함한 비동기 파일 인코딩

Node.js
import { readFile } from 'node:fs/promises'

async function encodeFileToBase64(filePath: string): Promise<string> {
  try {
    const buffer = await readFile(filePath)
    return buffer.toString('base64')
  } catch (err) {
    const code = (err as NodeJS.ErrnoException).code
    if (code === 'ENOENT') throw new Error(`File not found: ${filePath}`)
    if (code === 'EACCES') throw new Error(`Permission denied: ${filePath}`)
    throw err
  }
}

// Encode a PDF for an email attachment payload
const reportBase64 = await encodeFileToBase64('./reports/q1-financials.pdf')

const emailPayload = {
  to:          'finance-team@company.internal',
  subject:     'Q1 Financial Report',
  attachments: [{
    filename:    'q1-financials.pdf',
    content:     reportBase64,
    encoding:    'base64',
    contentType: 'application/pdf',
  }],
}

console.log(`Attachment: ${reportBase64.length} chars`)

JavaScript Base64 함수 — 매개변수 참조

Python의 base64 모듈과 달리, JavaScript에는 단일 통합 Base64 함수가 없습니다. 사용하는 API는 대상 환경에 따라 다릅니다. 모든 네이티브 방식의 완전한 참조가 여기 있습니다:

함수입력 타입UnicodeURL 안전사용 가능 환경
btoa(string)string (Latin-1)❌ U+00FF 초과 시 예외 발생❌ 수동 교체Browser, Node 16+, Bun, Deno
atob(string)Base64 string❌ 바이너리 문자열 반환❌ 수동 교체Browser, Node 16+, Bun, Deno
Buffer.from(src, enc) .toString(enc)string | Buffer | Uint8Array✅ utf8 인코딩✅ Node 18+의 base64urlNode.js, Bun
TextEncoder().encode(str) + btoa()string(모든 Unicode)✅ UTF-8 바이트 경유❌ 수동 교체Browser, Node 16+, Deno
Uint8Array.toBase64() (TC39)Uint8Array✅ 바이너리✅ omitPadding + alphabetChrome 130+, Node 22+

Buffer.from(src, enc).toString(enc) 시그니처는 Base64와 관련된 여러 인코딩 값을 받습니다:

"base64"
표준 Base64(RFC 4648 §4). +와 /를 사용하며 = 패딩 있음.
"base64url"
URL 안전 Base64(RFC 4648 §5, Node.js 18+). -와 _를 사용하며 패딩 없음.
"utf8"
문자열 소스의 기본값. 소스가 사람이 읽을 수 있는 텍스트인 경우 사용.
"binary"
Latin-1 / ISO-8859-1. 소스가 원시 바이너리 문자열(예: atob()에서 가져온)인 경우 사용.

URL 안전 Base64 — JWT, URL, 파일명을 위한 인코딩

표준 Base64는 + /를 사용하는데, 이들은 URL에서 예약된 문자입니다 — +는 쿼리 문자열에서 공백으로 디코딩되고, /는 경로 구분자입니다. JWT, URL 매개변수, 파일명, 쿠키 값은 모두 URL 안전 변형이 필요합니다:+-, /_, 후행 = 제거.

브라우저 — 수동 문자 교체

JavaScript (browser)
function toBase64Url(text: string): string {
  // For ASCII-safe input (e.g., JSON with only ASCII chars)
  return btoa(text)
    .replace(/+/g, '-')
    .replace(///g, '_')
    .replace(/=/g, '')
}

function fromBase64Url(encoded: string): string {
  // Restore standard Base64 characters and padding before decoding
  const base64  = encoded.replace(/-/g, '+').replace(/_/g, '/')
  const padded  = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
  return atob(padded)
}

// JWT header — must be URL-safe Base64
const header  = JSON.stringify({ alg: 'HS256', typ: 'JWT' })
const encoded = toBase64Url(header)
console.log(encoded) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

const decoded = fromBase64Url(encoded)
console.log(decoded) // {"alg":"HS256","typ":"JWT"}

Node.js 18+ — 네이티브 'base64url' 인코딩

Node.js 18+
// Node.js 18 added 'base64url' as a first-class Buffer encoding
const sessionPayload = JSON.stringify({
  userId:     'usr_9f2a1c3e8b4d',
  role:       'editor',
  workspaceId:'ws_3a7f91c2',
  exp:        Math.floor(Date.now() / 1000) + 3600,
})

const encoded = Buffer.from(sessionPayload, 'utf8').toString('base64url')
// No + or / or = characters in the output
// eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9

const decoded = Buffer.from(encoded, 'base64url').toString('utf8')
console.log(JSON.parse(decoded).role) // editor

JavaScript에서 파일 및 API 응답 인코딩

프로덕션 코드에서 Base64 인코딩은 주로 전송되는 파일과 바이너리 콘텐츠를 전달하는 외부 API의 응답에 적용됩니다. 브라우저와 Node.js에서 패턴이 다르며, 바이너리 데이터는 특별한 주의가 필요합니다.

브라우저 — input 요소에서 파일 인코딩

JavaScript (browser)
// Modern approach: File.arrayBuffer() (Chrome 76+, Firefox 69+, Safari 14+)
async function encodeFile(file: File): Promise<string> {
  const buffer = await file.arrayBuffer()
  const bytes  = new Uint8Array(buffer)
  const chars  = Array.from(bytes, b => String.fromCharCode(b))
  return btoa(chars.join(''))
}

const uploadInput = document.getElementById('avatar') as HTMLInputElement

uploadInput.addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (!file) return

  try {
    const encoded = await encodeFile(file)
    const dataUri = `data:${file.type};base64,${encoded}`

    // Preview the image inline
    const img   = document.getElementById('preview') as HTMLImageElement
    img.src     = dataUri
    img.hidden  = false

    console.log(`Encoded ${file.name} (${file.size} bytes) → ${encoded.length} Base64 chars`)
  } catch (err) {
    console.error('Encoding failed:', err)
  }
})

API에서 Base64 인코딩된 바이너리 가져오기

JavaScript
// GitHub Contents API returns file content as Base64 with embedded newlines
async function fetchRepoFile(
  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; size: number }

  if (data.encoding !== 'base64') {
    throw new Error(`Unexpected encoding from GitHub: ${data.encoding}`)
  }

  // GitHub wraps output at 60 chars — strip newlines before decoding
  const clean = data.content.replace(/\n/g, '')
  return atob(clean)
}

const openApiSpec = await fetchRepoFile(
  'acme-corp', 'platform-api', 'openapi.json', process.env.GITHUB_TOKEN!
)
const spec = JSON.parse(openApiSpec)
console.log(`API version: ${spec.info.version}`)

API 디버깅 중 스크립트 없이 인코딩된 응답을 검사하려면, Base64 값을 Base64 Encoder 에 직접 붙여넣으세요 — 디코딩도 지원하며 즉시 출력됩니다. GitHub API 응답, JWT 페이로드, webhook 서명 검사에 유용합니다.

Node.js와 셸에서 명령줄 Base64 인코딩

CI/CD 스크립트, Makefile 대상, 또는 일회성 디버깅에는 전체 스크립트가 거의 필요 없습니다. 시스템 도구와 Node.js 원라이너로 크로스 플랫폼에서 대부분의 경우를 커버할 수 있습니다.

bash
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard encoding
echo -n "deploy-bot:sk-prod-a7f2c91e4b3d8" | base64
# ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

# URL-safe variant (replace chars and strip padding)
echo -n "deploy-bot:sk-prod-a7f2c91e4b3d8" | base64 | tr '+/' '-_' | tr -d '='

# Encode a file inline (macOS: -b 0 removes line wrapping; Linux: --wrap=0)
base64 -b 0 ./config/production.json
# or on Linux:
base64 --wrap=0 ./config/production.json

# Decode
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 --decode

# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1]).toString('base64'))" "my:secret"
# bXk6c2VjcmV0

# URL-safe from Node.js 18+
node -e "process.stdout.write(Buffer.from(process.argv[1]).toString('base64url'))" "my:secret"
# bXk6c2VjcmV0  (same here since there are no special chars)

# Decode in Node.js
node -e "console.log(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
참고:macOS에서 base64는 기본적으로 76자에서 출력을 줄 바꿈합니다. 이로 인해 다운스트림 파싱이 깨집니다. 환경 변수나 config 필드에 쓸 때처럼 한 줄 결과가 필요한 경우, 반드시 -b 0(macOS) 또는 --wrap=0(Linux)을 추가하세요.

고성능 대안:js-base64

내장 API는 대부분의 사용 사례에 충분합니다. 라이브러리를 사용하는 주요 이유는 크로스 환경 일관성입니다:브라우저와 Node.js 모두에서 실행되는 패키지를 배포할 경우, Buffer를 사용하면 환경 감지나 번들러 설정이 필요하고,btoa()는 Unicode 우회 방법이 필요합니다.js-base64(npm 주간 다운로드 1억+)는 두 가지를 투명하게 처리합니다.

bash
npm install js-base64
# or
pnpm add js-base64
JavaScript
import { toBase64, fromBase64, toBase64Url, fromBase64Url, isValid } from 'js-base64'

// Standard encoding — Unicode-safe, works in browser and Node.js
const telemetryEvent = JSON.stringify({
  eventId:   'evt_7c3a9f1b2d',
  type:      'checkout_completed',
  currency:  'EUR',
  amount:    14900,
  userId:    'usr_4e2b8d6a5c',
  timestamp: 1717200000,
})

const encoded    = toBase64(telemetryEvent)
const urlEncoded = toBase64Url(telemetryEvent) // No +, /, or = characters

const decoded = fromBase64(encoded)
console.log(JSON.parse(decoded).type) // checkout_completed

// Binary data — pass a Uint8Array as second argument
const pngMagicBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
const binaryEncoded = toBase64(pngMagicBytes, true) // true = binary mode

// Validation before decoding
const suspicious = 'not!valid@base64#'
console.log(isValid(suspicious)) // false

내부적으로 js-base64는 사용 가능한 경우 네이티브 Buffer를 사용하고, 브라우저에서는 순수 JS 구현으로 폴백합니다. 대용량 Unicode 문자열에서 TextEncoder+btoa 방식보다 2–3배 빠르며, 대칭적인 API(toBase64 / fromBase64)는 btoa atob의 방향을 기억해야 하는 인지 부담을 없애줍니다.

Node.js 스트림으로 대용량 바이너리 파일 인코딩

약 50 MB보다 큰 파일을 인코딩해야 할 때, readFileSync()로 전체 파일을 메모리에 로드하는 것이 문제가 됩니다. Node.js 스트림을 사용하면 데이터를 청크로 처리할 수 있지만, Base64 인코딩에는 제약이 있습니다:청크 경계에서 잘못된 패딩을 방지하려면 인코더에 3바이트의 배수를 입력해야 합니다.

Node.js
import { createReadStream, createWriteStream } from 'node:fs'
import { pipeline } from 'node:stream/promises'

// Stream a large binary file to a Base64-encoded output file
async function streamEncodeToBase64(
  inputPath:  string,
  outputPath: string,
): Promise<void> {
  const readStream  = createReadStream(inputPath, { highWaterMark: 3 * 1024 * 256 }) // 768 KB chunks (multiple of 3)
  const writeStream = createWriteStream(outputPath, { encoding: 'utf8' })

  let buffer = Buffer.alloc(0)

  await pipeline(
    readStream,
    async function* (source) {
      for await (const chunk of source) {
        buffer = Buffer.concat([buffer, chunk as Buffer])

        // Encode in complete 3-byte groups to avoid mid-stream padding
        const remainder = buffer.length % 3
        const safe      = buffer.subarray(0, buffer.length - remainder)
        buffer          = buffer.subarray(buffer.length - remainder)

        if (safe.length > 0) yield safe.toString('base64')
      }
      // Flush remaining bytes (may add 1 or 2 '=' padding chars)
      if (buffer.length > 0) yield buffer.toString('base64')
    },
    writeStream,
  )
}

// Usage: encode a 200 MB video attachment
await streamEncodeToBase64(
  './uploads/product-demo.mp4',
  './dist/product-demo.b64',
)
console.log('Stream encoding complete')
참고:출력 중간에 잘못된 = 패딩이 생기지 않도록 청크 크기는 3바이트의 배수여야 합니다. 예제에서는 3 * 1024 * 256 = 786,432바이트(768 KB)를 사용합니다 — 메모리 예산에 따라 highWaterMark를 조정하세요. 50 MB 미만 파일에는 readFile() + Buffer.toString('base64')가 더 간단하고 충분히 빠릅니다.

일반적인 실수

Base64 인코딩이 포함된 많은 JavaScript 코드베이스를 검토해왔는데, 이 4가지 실수가 일관되게 나타납니다 — 종종 비 ASCII 문자나 바이너리 파일이 프로덕션의 인코딩 경로에 도달할 때까지 발견되지 않습니다.

실수 1 — Unicode를 btoa()에 직접 전달

문제: btoa()는 코드 포인트 0–255의 문자만 허용합니다.ñ, 이모지, CJK 한자 같은 문자는 즉시 DOMException을 발생시킵니다. 해결책: 먼저 TextEncoder로 인코딩하거나, Node.js에서 Buffer.from(text, 'utf8').toString('base64')를 사용하세요.

Before · JavaScript
After · JavaScript
// ❌ DOMException: The string to be encoded contains
//    characters outside of the Latin1 range
const username = 'Алексей Иванов'
const encoded  = btoa(username)  // throws
// ✅ Encode as UTF-8 bytes first
function safeEncode(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, b => String.fromCharCode(b))
  return btoa(chars.join(''))
}
const encoded = safeEncode('Алексей Иванов')
// 0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy

실수 2 — atob() 호출 전 패딩 복원 잊기

문제: URL 안전 Base64는 = 패딩을 제거합니다. 패딩 없는 문자열을 atob()에 직접 전달하면 문자열 길이에 따라 잘못된 출력이나 예외가 발생합니다. 해결책: atob()를 호출하기 전에 +/를 복원하고 올바른 양의 패딩을 다시 추가하세요.

Before · JavaScript
After · JavaScript
// ❌ atob() may return wrong data or throw
//    on URL-safe Base64 without padding
const jwtSegment = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ'
const decoded    = atob(jwtSegment) // Unreliable
// ✅ Restore characters 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)
  return atob(pad)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"}

실수 3 — 원시 버퍼 대신 인코딩된 청크 연결

문제: btoa() .toString('base64')를 호출할 때마다 자체 패딩이 추가됩니다. 두 개의 패딩된 Base64 문자열을 연결하면, 패딩은 맨 끝에만 있어야 하므로 잘못된 출력이 생성됩니다. 해결책: 인코딩하기 전에 원시 데이터를 연결하세요.

Before · JavaScript
After · JavaScript
// ❌ Both parts are padded independently —
//    the combined string is not valid Base64
const part1 = Buffer.from('webhook-secret').toString('base64')
// d2ViaG9vay1zZWNyZXQ=  ← has padding
const part2 = Buffer.from('-v2').toString('base64')
// LXYy            ← correct in isolation
const combined = part1 + part2 // ❌ Invalid — padding in the middle
// ✅ Concatenate raw Buffers before encoding
const combined = Buffer.concat([
  Buffer.from('webhook-secret'),
  Buffer.from('-v2'),
]).toString('base64')
// d2ViaG9vay1zZWNyZXQtdjI= — single valid Base64 string

실수 4 — 인코딩 전 response.text()로 바이너리 API 데이터 읽기

문제: response.text()는 원시 바이트를 UTF-8로 해석하고 인식할 수 없는 바이트 시퀀스를 대체 문자 U+FFFD로 바꿉니다. 이미지, PDF, 오디오 등의 바이너리 콘텐츠는 btoa()에 도달하기 전에 조용히 손상됩니다. 해결책: 원시 바이트를 얻으려면 response.arrayBuffer()를 사용하세요.

Before · JavaScript
After · JavaScript
// ❌ response.text() corrupts binary data
const res     = await fetch('/api/exports/invoice.pdf')
const text    = await res.text()   // ❌ PDF bytes mangled as UTF-8
const encoded = btoa(text)         // ❌ Corrupted Base64
// ✅ arrayBuffer() preserves raw bytes
const res     = await fetch('/api/exports/invoice.pdf')
const buffer  = await res.arrayBuffer()
const bytes   = new Uint8Array(buffer)
const chars   = Array.from(bytes, b => String.fromCharCode(b))
const encoded = btoa(chars.join('')) // ✅ Valid Base64

JavaScript Base64 메서드 — 빠른 비교

메서드Unicode바이너리 데이터URL 안전사용 가능 환경설치 필요
btoa() / atob()❌ Latin-1❌ 우회 방법 필요❌ 수동 교체Browser, Node 16+, Bun, Deno아니오
TextEncoder + btoa()✅ UTF-8✅ Uint8Array 경유❌ 수동 교체Browser, Node 16+, Deno아니오
Buffer.from().toString()✅ utf8✅ 네이티브✅ base64url (Node 18+)Node.js, Bun아니오
Uint8Array.toBase64() (TC39)✅ 바이너리✅ 네이티브✅ alphabet 옵션Chrome 130+, Node 22+아니오
js-base64✅ 항상✅ Uint8Array✅ 내장유니버설npm install

입력이 확실히 ASCII만인 경우(16진수 다이제스트, 숫자 ID, 사전 검증된 Latin-1 문자열)에만 btoa()를 선택하세요. 브라우저에서 사용자가 제공한 텍스트에는 TextEncoder + btoa()를 사용하세요. 모든 Node.js 서버 사이드 코드에는 Buffer가 올바른 기본값입니다. 번들러 설정 없이 두 환경에서 실행되어야 하는 라이브러리에는 js-base64가 모든 엣지 케이스를 제거합니다.

자주 묻는 질문

왜 btoa()가 내 문자열에 "InvalidCharacterError"를 발생시키나요?
btoa()는 코드 포인트가 0–255 범위(Latin-1 / ISO-8859-1)인 문자만 허용합니다. U+00FF를 초과하는 모든 문자 — 대부분의 키릴 문자, 아랍 문자, CJK 한자, 많은 이모지 포함 — 는 DOMException을 발생시킵니다. 수정 방법은 환경에 따라 다릅니다: 브라우저에서는 먼저 TextEncoder로 UTF-8 바이트로 인코딩하고, String.fromCharCode()로 각 바이트를 문자로 변환한 후 btoa()를 호출합니다. Node.js에서는 Unicode를 네이티브로 처리하는 Buffer.from(text, 'utf8').toString('base64')를 사용합니다.
btoa()는 Node.js에서 임포트 없이 사용할 수 있나요?
네, Node.js 16.0부터 가능합니다. btoa()와 atob() 모두 전역 함수로 등록되어 있습니다 — 임포트 불필요합니다. Latin-1 제한을 포함하여 브라우저의 대응 함수와 동일하게 동작합니다. Node.js 서버 코드에서는 UTF-8을 네이티브로 처리하고, 우회 방법 없이 바이너리 데이터를 지원하며, Node.js 18에서 추가된 'base64url' 인코딩 옵션이 있어 btoa() 대신 Buffer.from()이 여전히 선호됩니다.
표준 Base64와 URL 안전 Base64의 차이점은 무엇인가요?
표준 Base64(RFC 4648 §4)는 값 62에 +, 값 63에 /, 패딩에 =를 사용합니다. 이 문자들은 URL에서 특별한 의미를 가집니다: +는 쿼리 문자열에서 공백으로 해석되고, /는 경로 구분자입니다. URL 안전 Base64(RFC 4648 §5)는 +를 -로, /를 _로 대체하며 일반적으로 = 패딩을 완전히 생략합니다. JWT는 세 세그먼트 모두에 URL 안전 Base64를 사용합니다. Node.js 18+에서 Buffer.from(text).toString('base64url')이 URL 안전 형식을 직접 생성합니다.
JavaScript에서 CSS data URI용 이미지를 Base64로 인코딩하는 방법은?
브라우저에서: file.arrayBuffer()로 바이너리를 읽고, Uint8Array로 변환한 후, btoa(Array.from(bytes, b => String.fromCharCode(b)).join(''))를 호출합니다. data URI를 'data:' + file.type + ';base64,' + encoded로 구성합니다. Node.js에서: const encoded = fs.readFileSync('./image.png').toString('base64')하고 MIME 타입을 앞에 추가합니다. SVG 파일의 경우 종종 Base64를 완전히 건너뛰고 URL 인코딩된 data URI를 사용할 수 있습니다 — 더 읽기 쉽고 약간 더 작습니다.
브라우저에서 npm 라이브러리 없이 Base64 인코딩/디코딩이 가능한가요?
네. ASCII만 있는 입력에는 btoa()와 atob()를 직접 사용할 수 있습니다. Unicode에는 TextEncoder / TextDecoder 쌍이 완전한 도구 세트를 제공합니다 — 둘 다 모든 최신 브라우저와 Node.js 16+에 내장되어 있습니다. 라이브러리가 진정한 가치를 발휘하는 유일한 경우는 크로스 환경 일관성이 필요할 때입니다: 번들러 설정 없이 브라우저와 Node.js 모두에서 동일하게 동작해야 하는 유틸리티를 작성한다면, js-base64가 환경 감지 로직을 제거합니다.
GitHub API에서 반환된 Base64 콘텐츠를 디코딩하는 방법은?
GitHub Contents API는 파일 콘텐츠를 내장 줄 바꿈 문자가 포함된 Base64로 반환합니다(API가 60자에서 출력을 래핑합니다). 디코딩 전에 줄 바꿈을 제거합니다: const clean = data.content.replace(/\n/g, ''); const text = atob(clean);. Node.js에서: const text = Buffer.from(data.content.replace(/\n/g, ''), 'base64').toString('utf8');. GitHub는 항상 표준 Base64를 사용합니다(URL 안전 아님), 따라서 + → - 또는 / → _ 대체가 필요 없습니다.

관련 도구

코드 작성 없이 원클릭으로 인코딩하거나 디코딩하려면, 문자열이나 바이너리를 Base64 Encoder 에 직접 붙여넣으세요 — 브라우저에서 표준 및 URL 안전 모드를 즉시 처리합니다.

다른 언어로도 제공됩니다:PythonJava
AC
Alex ChenFront-end & Node.js Developer

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.

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.