JavaScript Base64 エンコードガイド:btoa() と Buffer の使い方

·Front-end & Node.js Developer·レビュー担当Sophie Laurent·公開日

無料の Base64エンコーダー をブラウザで直接使用 — インストール不要。

Base64エンコーダー をオンラインで試す →

CSS の data URI に画像を埋め込んだり、HTTP Authorization ヘッダーに認証情報を渡したり、 バイナリ証明書を環境変数に保存したりする際、ブラウザと Node.js の両方で JavaScript データを 確実に Base64 エンコードする必要があります。JavaScript には 2 つの異なる組み込み 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 は暗号化ではありません — 機密性は提供されません。 エンコードされた文字列を持っている人であれば誰でも、1 回の関数呼び出しでデコードできます。 その目的はトランスポートの安全性です:多くのプロトコルやストレージ形式は 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 でもネイティブに動作します。インポートは不要です。

この関数は文字列引数を 1 つ受け取り、その 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)
// 56K644GX44G/77ya55Sw5Lit5aSq6YOO4oCU5p2x5Lqs5YCJ5bqr44CB5pWZ6K+V77yaMjUw

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 パラメータ、 ファイル名、Cookie の値はすべて 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 文字で出力を折り返します。 これにより下流のパースが壊れます。環境変数やコンフィグフィールドへの書き込みなど、 単一行の結果が必要な場合は、必ず -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') を呼び出すたびに、 独自のパディングが追加されます。2 つのパディング付き 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 はすべての 3 つのセグメントに 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.