Base64 в JavaScript: btoa(), Buffer и Unicode

·Front-end & Node.js Developer·ПровереноSophie Laurent·Опубликовано

Используйте бесплатный Base64 Encode Online прямо в браузере — установка не требуется.

Попробовать Base64 Encode Online онлайн →

Когда вы встраиваете изображение в data URI CSS, передаёте учётные данные в HTTP-заголовке Authorization или храните бинарный сертификат в переменной окружения, вам необходимо надёжно кодировать данные в Base64 в JavaScript — как в браузере, так и в Node.js. JavaScript предоставляет два встроенных API:btoa() для браузерных окружений (также доступен в Node.js 16+) и Buffer.from() для Node.js — каждый со своими ограничениями по Unicode, бинарным данным и URL-безопасности. Для быстрого однократного кодирования без написания кода Base64 Encoder от ToolDeck справляется мгновенно в браузере. В этом руководстве рассматриваются оба окружения с готовыми к продакшену примерами: обработка Unicode, URL-безопасные варианты, кодирование файлов и ответов API, использование CLI и четыре ошибки, которые постоянно вызывают баги в реальных проектах.

  • btoa() встроен в браузер и доступен в Node.js 16+ глобально, но принимает только Latin-1 (кодовые точки 0–255) — Unicode-ввод вызывает DOMException
  • Buffer.from(text, "utf8").toString("base64") — эквивалент для Node.js, нативно поддерживающий Unicode без дополнительных шагов
  • URL-безопасный Base64 заменяет + → -, / → _ и убирает padding = — используйте Buffer.from().toString("base64url") в Node.js 18+ для одной строки кода
  • Для бинарных данных (ArrayBuffer, Uint8Array, файлы) используйте Buffer в Node.js или подход 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, добавляется один или два символа = для выравнивания. Закодированный вывод всегда примерно на 33% больше оригинала.

Base64 — это не шифрование: оно не обеспечивает конфиденциальность. Любой, у кого есть закодированная строка, может декодировать её одним вызовом функции. Цель Base64 — транспортная совместимость: многие протоколы и форматы хранения разработаны для 7-битного ASCII-текста и не могут работать с произвольными бинарными байтами. Base64 устраняет это ограничение. Распространённые случаи использования в JavaScript: data URI для встраивания ресурсов, заголовки HTTP Basic Auth, сегменты JWT-токенов, вложения MIME в электронных письмах и хранение бинарных блобов в JSON API.

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

btoa() — встроенная функция кодирования для браузеров

btoa() (binary-to-ASCII) доступна в браузерах начиная с IE10 и стала глобальной в Node.js 16.0 в рамках инициативы WinterCG. Она также работает нативно в Deno, Bun и Cloudflare Workers. Импорт не требуется.

Функция принимает один строковый аргумент и возвращает его в кодировке Base64. Симметричная функция atob() (ASCII-to-binary) декодирует его обратно. Обе функции синхронны и работают с постоянным объёмом памяти относительно размера входных данных.

Минимальный рабочий пример

JavaScript (browser / Node.js 16+)
// Кодирование пары учётных данных API для заголовка HTTP Basic Auth
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
// Туда и обратно: кодирование, передача, декодирование
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+)
// Вспомогательные функции для Unicode-безопасного 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)
}

// Работает с любым языком или алфавитом
const orderNote = 'Подтверждено: Алексей Иванов — склад в Санкт-Петербурге, кол-во: 250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(encoded)
// 0J/QvtC00YLQstC10YDQttC00LU6INCQ0LvQtdC60YHQtdC5INCYQeKAkyDRgdC60LvQsNC0INCyINCh0LDQvdC60YIt0J/QtdGC0LXRgNCx0YPRgNCz0LUsINC60L7Qu...

console.log(decoded === orderNote) // true
Примечание:Если вы уже в Node.js, пропустите обходной путь с TextEncoder — используйте Buffer.from(text, 'utf8').toString('base64'). Это обрабатывает Unicode нативно и работает быстрее для больших строк.

Buffer.from() в Node.js — полное руководство с примерами

В Node.js Buffer — идиоматический API для всех операций с бинарными данными, включая конвертацию кодировок. Он появился раньше TextEncoder на несколько лет и остаётся предпочтительным выбором для серверного кода. Ключевые преимущества перед btoa(): нативная поддержка UTF-8, работа с бинарными данными и сокращение 'base64url' доступное с Node.js 18.

Базовое кодирование и декодирование текста

Node.js
// Кодирование объекта конфигурации сервера для хранения в переменной окружения
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...

// Декодирование обратно
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'

// Читаем TLS-сертификат и кодируем его для встраивания в конфигурационный файл
const certPem     = readFileSync(join(process.cwd(), 'ssl', 'server.crt'))
const certBase64  = certPem.toString('base64')

// Сохраняем как однострочный — подходит для переменных окружения или JSON-конфигов
writeFileSync('./dist/cert.b64', certBase64, 'utf8')

console.log(`Сертификат закодирован: ${certBase64.length} символов`)
// Сертификат закодирован: 2856 символов

// Восстанавливаем бинарный сертификат из закодированного значения
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(`Файл не найден: ${filePath}`)
    if (code === 'EACCES') throw new Error(`Нет прав доступа: ${filePath}`)
    throw err
  }
}

// Кодируем PDF для вложения в email
const reportBase64 = await encodeFileToBase64('./reports/q1-financials.pdf')

const emailPayload = {
  to:          'finance-team@company.internal',
  subject:     'Финансовый отчёт за Q1',
  attachments: [{
    filename:    'q1-financials.pdf',
    content:     reportBase64,
    encoding:    'base64',
    contentType: 'application/pdf',
  }],
}

console.log(`Вложение: ${reportBase64.length} символов`)

Функции Base64 в JavaScript — справочник параметров

В отличие от модуля base64 в Python, в JavaScript нет единой унифицированной функции Base64. API зависит от целевого окружения. Вот полный справочник всех нативных подходов:

ФункцияТип входных данныхUnicodeURL-безопасностьДоступно в
btoa(string)string (Latin-1)❌ throws above U+00FF❌ manual replaceBrowser, Node 16+, Bun, Deno
atob(string)Base64 string❌ returns binary string❌ manual replaceBrowser, Node 16+, Bun, Deno
Buffer.from(src, enc) .toString(enc)string | Buffer | Uint8Array✅ utf8 encoding✅ base64url in Node 18+Node.js, Bun
TextEncoder().encode(str) + btoa()string (any Unicode)✅ via UTF-8 bytes❌ manual replaceBrowser, Node 16+, Deno
Uint8Array.toBase64() (TC39)Uint8Array✅ binary✅ 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

Кодирование файлов и ответов API в JavaScript

В production-коде кодирование 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}`

    // Предварительный просмотр изображения
    const img   = document.getElementById('preview') as HTMLImageElement
    img.src     = dataUri
    img.hidden  = false

    console.log(`Закодировано ${file.name} (${file.size} байт) → ${encoded.length} символов Base64`)
  } catch (err) {
    console.error('Ошибка кодирования:', err)
  }
})

Получение бинарных данных в Base64 из API

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.

Кодирование Base64 через командную строку в Node.js и Shell

Для скриптов 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 (100M+ загрузок в неделю на npm) решает это прозрачно.

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 в браузере. Он в 2–3 раза быстрее подхода TextEncoder+btoa для больших Unicode-строк, а симметричный API (toBase64 / fromBase64) устраняет необходимость помнить, в какую сторону работают btoa и atob.

Кодирование больших бинарных файлов с помощью потоков Node.js

Когда нужно закодировать файлы размером больше ~50 МБ, загрузка всего файла в память с помощью 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('Потоковое кодирование завершено')
Примечание:Размер чанка должен быть кратен 3 байтам, чтобы избежать лишнего = паддинга в середине вывода. В примере используется 3 * 1024 * 256 = 786 432 байта (768 КБ) — скорректируйте highWaterMark в зависимости от бюджета памяти. Для файлов до 50 МБ readFile() + Buffer.toString('base64') проще и достаточно быстро.

Распространённые ошибки

Я проанализировал много JavaScript-проектов с кодированием Base64, и эти четыре ошибки встречаются постоянно — зачастую остаются незамеченными до тех пор, пока символ non-ASCII или бинарный файл не попадает в путь кодирования в production.

Ошибка 1 — Передача Unicode напрямую в btoa()

Проблема: btoa() принимает только символы с кодовыми точками 0–255. Символы вроде ñ, эмодзи или CJK-иероглифы вызывают немедленную DOMException. Решение: сначала закодируйте с помощью TextEncoder, или используйте Buffer.from(text, 'utf8').toString('base64') в Node.js.

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

Методы Base64 в JavaScript — быстрое сравнение

МетодUnicodeБинарные данныеURL-безопасностьОкруженияТребует установки
btoa() / atob()❌ Latin-1❌ workaround needed❌ manual replaceBrowser, Node 16+, Bun, DenoНет
TextEncoder + btoa()✅ UTF-8✅ via Uint8Array❌ manual replaceBrowser, Node 16+, DenoНет
Buffer.from().toString()✅ utf8✅ native✅ base64url (Node 18+)Node.js, BunНет
Uint8Array.toBase64() (TC39)✅ binary✅ native✅ alphabet optionChrome 130+, Node 22+Нет
js-base64✅ always✅ Uint8Array✅ built-inUniversalnpm install

Используйте btoa() только тогда, когда входные данные заведомо содержат только ASCII — хеш-дайджесты, числовые ID или предварительно проверенные строки Latin-1. Для пользовательского текста в браузере используйте TextEncoder + btoa(). Для всего серверного кода Node.js Buffer — правильный выбор по умолчанию. Для библиотек, которые должны работать в обоих окружениях без настройки бандлера, js-base64 устраняет все граничные случаи.

Часто задаваемые вопросы

Почему btoa() выбрасывает "InvalidCharacterError" для моей строки?
btoa() принимает только символы с кодовыми точками в диапазоне 0–255 (Latin-1 / ISO-8859-1). Любой символ выше U+00FF — включая большинство кириллических, арабских, CJK-иероглифов и многие эмодзи — вызывает DOMException. Решение зависит от окружения: в браузере сначала закодируйте в байты UTF-8 с TextEncoder, преобразуйте каждый байт в символ с String.fromCharCode(), затем вызовите btoa(). В Node.js используйте Buffer.from(text, 'utf8').toString('base64'), который поддерживает Unicode нативно.
Доступен ли btoa() в Node.js без какого-либо импорта?
Да, начиная с Node.js 16.0. Обе функции — btoa() и atob() — зарегистрированы как глобальные — никакого импорта не нужно. Они ведут себя идентично своим браузерным аналогам, включая ограничение Latin-1. Для серверного кода Node.js Buffer.from() всё равно предпочтительнее btoa(), потому что он поддерживает UTF-8 нативно, работает с бинарными данными без обходных путей и имеет опцию 'base64url', добавленную в Node.js 18.
В чём разница между стандартным 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-безопасный формат.
Как закодировать изображение в Base64 для data URI в CSS на JavaScript?
В браузере: используйте 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 и использовать data URI с URL-кодированием — это читаемее и немного компактнее.
Можно ли кодировать и декодировать Base64 без npm-библиотек в браузере?
Да. Для ASCII-ввода btoa() и atob() работают напрямую. Для Unicode пара TextEncoder / TextDecoder даёт полный инструментарий — оба встроены во все современные браузеры и Node.js 16+. Единственный случай, когда библиотека действительно добавляет ценность — кроссокружная совместимость: если вы пишете утилиту, которая должна работать одинаково в браузере и Node.js без настройки бандлера, js-base64 убирает логику определения окружения.
Как декодировать содержимое Base64 из GitHub API?
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.