Base64 في JavaScript — btoa() وBuffer

·Front-end & Node.js Developer·مراجعة بواسطةSophie Laurent·نُشر

استخدم مشفّر Base64 عبر الإنترنت المجاني مباشرةً في متصفحك — لا حاجة للتثبيت.

جرّب مشفّر Base64 عبر الإنترنت أونلاين ←

عندما تضمّن صورة في CSS data URI، أو تمرر بيانات اعتماد في رأس HTTP Authorization، أو تخزّن شهادة ثنائية في متغير بيئة، تحتاج إلى ترميز Base64 لبيانات JavaScript بشكل موثوق عبر المتصفح وNode.js. يوفر JavaScript واجهتي برمجة تطبيقات مدمجتين مختلفتين:btoa() لبيئات المتصفح (متاحة أيضاً في Node.js 16+) وBuffer.from() لـ Node.js — كلٌّ منهما بقيود مختلفة حول Unicode والبيانات الثنائية وسلامة الروابط. لترميز سريع دون كتابة أي كود، مُرمِّز Base64 في ToolDeck يتعامل معه فوراً في المتصفح. يغطي هذا الدليل كلا البيئتين مع أمثلة جاهزة للإنتاج: معالجة Unicode، والمتغيرات الآمنة للروابط، وترميز الملفات واستجابات API، واستخدام CLI، والأخطاء الأربعة التي تسبب أخطاء باستمرار في قواعد الكود الحقيقية.

  • الدالة btoa() متاحة نصياً في المتصفح وفي Node.js 16+ بشكل عالمي، لكنها تقبل فقط Latin-1 (نقاط الكود 0–255) — إدخال Unicode يُطلق استثناء DOMException
  • الدالة Buffer.from(text, "utf8").toString("base64") هي المكافئ في Node.js وتعالج Unicode بشكل أصلي دون خطوات إضافية
  • Base64 الآمن للروابط يستبدل + بـ - و / بـ _ ويحذف حشو = — استخدم Buffer.from().toString("base64url") في Node.js 18+ للحصول على سطر واحد
  • للبيانات الثنائية (ArrayBuffer، Uint8Array، الملفات)، استخدم Buffer في Node.js أو نهج arrayBuffer() + Uint8Array في المتصفح — لا تستخدم response.text() أبداً
  • الدالة Uint8Array.prototype.toBase64() (TC39 المرحلة 3) متاحة بالفعل في Node.js 22+ وChrome 130+ وستوحد البيئتين

ما هو ترميز Base64؟

يحوّل Base64 البيانات الثنائية الاعتباطية إلى سلسلة مبنية من 64 حرفاً ASCII قابلاً للطباعة: A–Z، a–z، 0–9، +، و /. كل 3 بايت من الإدخال تُعيَّن إلى 4 أحرف Base64 بالضبط؛ إذا لم يكن طول الإدخال مضاعفاً للعدد 3، يُضاف حرف أو حرفا حشو =. الناتج المُرمَّز دائماً أكبر بنحو 33% من الأصل.

Base64 ليس تشفيراً — لا يوفر أي سرية. يمكن لأي شخص يمتلك السلسلة المُرمَّزة فك ترميزها باستدعاء دالة واحدة. غرضه هو سلامة النقل: كثير من البروتوكولات وصيغ التخزين صُممت لنص ASCII ذي 7 بتات ولا تستطيع التعامل مع بايتات ثنائية اعتباطية. يسد Base64 هذه الفجوة. تشمل حالات الاستخدام الشائعة في JavaScript: URIs للبيانات لتضمين الأصول، رؤوس HTTP Basic Auth، أجزاء رمز JWT، مرفقات البريد الإلكتروني MIME، وتخزين النقط الثنائية في JSON APIs.

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

btoa() — دالة الترميز الأصلية للمتصفح

btoa() (ثنائي إلى ASCII) متاحة في المتصفحات منذ IE10 وأصبحت عالمية في Node.js 16.0 كجزء من مبادرة توافق WinterCG. كما تعمل بشكل أصلي في Deno وBun وCloudflare Workers. لا حاجة لأي استيراد.

تأخذ الدالة وسيطة نصية واحدة وتُعيد شكلها المُرمَّز بـ Base64. النظير المتماثل atob() (ASCII إلى ثنائي) يفك ترميزه مجدداً. كلاهما متزامن ويعمل بذاكرة ثابتة نسبةً إلى حجم الإدخال.

مثال أدنى يعمل

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)
// 2KrZhSDYp9mE2KrYo9qp2K86INmF2K3ZhdWryp8g2KfZhNij2K3ZhdWryp8g4oCTINmF2LPYqtaqyp8g2KfZhNix2YrYp6...

console.log(decoded === orderNote) // true
ملاحظة:إذا كنت بالفعل في Node.js، تخطَّ حل TextEncoder تماماً — استخدم Buffer.from(text, 'utf8').toString('base64'). إنه يعالج Unicode بشكل أصلي وأسرع للسلاسل الكبيرة.

Buffer.from() في Node.js — دليل كامل مع أمثلة

في Node.js، Buffer هي واجهة برمجة التطبيقات المثالية لجميع عمليات البيانات الثنائية، بما في ذلك تحويلات الترميز. إنها تسبق TextEncoder بسنوات وتظل الخيار المفضل للكود من جانب الخادم. المزايا الرئيسية على btoa(): دعم UTF-8 الأصلي، ومعالجة البيانات الثنائية، واختصار ترميز 'base64url' المتاح منذ Node.js 18.

ترميز وفك ترميز النص الأساسي

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`)

دوال Base64 في JavaScript — مرجع المعاملات

على عكس وحدة base64 في Python، لا تمتلك JavaScript دالة Base64 موحدة واحدة. تعتمد واجهة برمجة التطبيقات على البيئة المستهدفة. إليك المرجع الكامل لجميع النهج الأصلية:

الدالةنوع الإدخالUnicodeآمن للروابطمتاح في
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✅ base64url في Node 18+Node.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"
Base64 الآمن للروابط (RFC 4648 §5، Node.js 18+). يستخدم - و_ بدون حشو.
"utf8"
الافتراضي لمصادر السلاسل. استخدمه عندما يكون المصدر نصاً يقرأه الإنسان.
"binary"
Latin-1 / ISO-8859-1. يُستخدم عندما يكون المصدر سلسلة ثنائية خام (مثلاً من atob()).

Base64 الآمن للروابط — الترميز لـ JWTs والروابط وأسماء الملفات

يستخدم Base64 القياسي + و /، وهما محجوزان في الروابط — + يُفك ترميزه كمسافة في سلاسل الاستعلام، و / هو فاصل المسار. تتطلب JWTs ومعاملات الروابط وأسماء الملفات وقيم الكوكيز المتغير الآمن للروابط: +-، /_، إزالة = اللاحق.

المتصفح — استبدال الأحرف اليدوي

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

في كود الإنتاج، يُطبَّق ترميز 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)
  }
})

جلب ملف ثنائي مُرمَّز بـ 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 — يفك الترميز أيضاً مع إخراج فوري. مفيد لفحص استجابات GitHub API، وحمولات JWT، وتوقيعات webhook.

ترميز Base64 من سطر الأوامر في Node.js والشيل

لسكريبتات 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

واجهات برمجة التطبيقات المدمجة مناسبة لمعظم حالات الاستخدام. السبب الرئيسي للجوء إلى مكتبة هو الاتساق عبر البيئات: إذا شحنت حزمة تعمل في المتصفح وNode.js معاً، فإن استخدام Buffer يتطلب إما اكتشاف البيئة أو إعداد المجمّع، بينما تتطلب btoa() حل Unicode. تتعامل js-base64 (أكثر من 100 مليون تنزيل أسبوعي على 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 الأصلي عند توفره وتعود إلى تنفيذ JavaScript الخالص في المتصفح. إنها أسرع بـ 2–3× من نهج TextEncoder+btoa للسلاسل Unicode الكبيرة، وواجهة برمجة التطبيقات المتماثلة (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('Stream encoding complete')
ملاحظة:يجب أن يكون حجم الجزء مضاعفاً لـ 3 بايت لتجنب حشو = الزائف في منتصف الإخراج. يستخدم المثال 3 * 1024 * 256 = 786,432 بايت (768 كيلوبايت) — اضبط highWaterMark بناءً على ميزانية ذاكرتك. للملفات أقل من 50 ميغابايت، readFile() + Buffer.toString('base64') أبسط وسريع بما يكفي.

الأخطاء الشائعة

راجعت كثيراً من قواعد كود JavaScript مع ترميز Base64، وتظهر هذه الأخطاء الأربعة باستمرار — غالباً غير مكتشفة حتى يصل حرف غير ASCII أو ملف ثنائي إلى مسار الترميز في الإنتاج.

الخطأ 1 — تمرير Unicode مباشرةً إلى btoa()

المشكلة: btoa() تقبل فقط الأحرف بنقاط الكود 0–255. الأحرف مثل ñ، أو الإيموجي، أو الحروف الصينية/اليابانية/الكورية تتسبب في DOMException فوري. الحل: رمِّز باستخدام TextEncoder أولاً، أو استخدم Buffer.from(text, 'utf8').toString('base64') في Node.js.

After · JavaScript
Before · JavaScript
// ✅ 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('محمد الأحمد')
// 2YXYrdmF2K8g2KfZhNij2K3ZhdWr
// ❌ DOMException: The string to be encoded contains
//    characters outside of the Latin1 range
const username = 'محمد الأحمد'
const encoded  = btoa(username)  // throws

الخطأ 2 — نسيان استعادة الحشو قبل atob()

المشكلة: يحذف Base64 الآمن للروابط حشو =. تمرير السلسلة المجردة مباشرةً إلى atob() ينتج إخراجاً غير صحيح أو يُطلق استثناء بحسب طول السلسلة. الحل: استعد + و/وأعد إضافة الحشو الصحيح قبل استدعاء atob().

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

الخطأ 3 — تسلسل الأجزاء المُرمَّزة بدلاً من المخازن الخام

المشكلة: كل استدعاء لـ btoa() أو .toString('base64') يضيف حشوه الخاص. تسلسل سلسلتي Base64 مُحشوَّتين ينتج إخراجاً غير صالح لأن الحشو ينتمي فقط في النهاية. الحل: تسلسل البيانات الخام قبل الترميز.

After · JavaScript
Before · JavaScript
// ✅ Concatenate raw Buffers before encoding
const combined = Buffer.concat([
  Buffer.from('webhook-secret'),
  Buffer.from('-v2'),
]).toString('base64')
// d2ViaG9vay1zZWNyZXQtdjI= — single valid Base64 string
// ❌ 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

الخطأ 4 — استخدام response.text() لقراءة بيانات API الثنائية قبل الترميز

المشكلة: response.text() يفسر البايتات الخام بتنسيق UTF-8 ويستبدل تسلسلات البايت غير المعروفة بحرف الاستبدال U+FFFD. أي محتوى ثنائي — صور وملفات PDF وصوت — يتلف بصمت قبل وصوله إلى btoa(). الحل: استخدم response.arrayBuffer() للحصول على البايتات الخام.

After · JavaScript
Before · JavaScript
// ✅ 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
// ❌ 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

طرق Base64 في JavaScript — مقارنة سريعة

الطريقةUnicodeبيانات ثنائيةآمن للروابطالبيئاتيتطلب تثبيت
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)✅ ثنائي✅ أصلي✅ خيار alphabetChrome 130+, Node 22+لا
js-base64✅ دائماً✅ Uint8Array✅ مدمجعالميnpm install

اختر btoa() فقط عندما يكون الإدخال مضموناً بأنه ASCII فقط — ملخصات hex أو معرّفات رقمية أو سلاسل Latin-1 محققة مسبقاً. للنصوص التي يوفرها المستخدم في المتصفح، استخدم TextEncoder + btoa(). لجميع كود Node.js من جانب الخادم، Buffer هو الخيار الافتراضي الصحيح. للمكتبات التي تحتاج إلى التشغيل في كلتا البيئتين دون تكوين المجمّع، js-base64 يزيل جميع الحالات الطرفية.

الأسئلة الشائعة

لماذا تُطلق btoa() خطأ "InvalidCharacterError" على سلسلتي؟
تقبل btoa() فقط الأحرف بنقاط كود في النطاق 0–255 (Latin-1 / ISO-8859-1). أي حرف فوق U+00FF — بما في ذلك معظم الحروف السيريلية والعربية والحروف الصينية/اليابانية/الكورية وكثير من الإيموجي — يتسبب في 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 القياسي وBase64 الآمن للروابط؟
يستخدم Base64 القياسي (RFC 4648 §4) + للقيمة 62 و/ للقيمة 63 و= للحشو. لهذه الأحرف معنى خاص في الروابط: + تُفسَّر كمسافة في سلاسل الاستعلام، و/ هو فاصل المسار. يستبدل Base64 الآمن للروابط (RFC 4648 §5) - بـ + و_ بـ /، ويحذف حشو = عادةً كلياً. تستخدم JWTs Base64 الآمن للروابط لجميع الأجزاء الثلاثة. في Node.js 18+، يُنتج Buffer.from(text).toString('base64url') التنسيق الآمن للروابط مباشرةً.
كيف أُرمِّز صورة بـ Base64 لـ CSS data URI في 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 مُرمَّز بالرابط بدلاً من ذلك، وهو أكثر قابلية للقراءة وأصغر حجماً.
هل يمكنني الترميز وفك الترميز بـ 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 القياسي (وليس الآمن للروابط)، لذا لا حاجة لاستبدال + → - أو / → _.

أدوات ذات صلة

لترميز أو فك ترميز بنقرة واحدة دون كتابة أي كود، الصق سلسلتك أو ملفك الثنائي مباشرةً في مُرمِّز Base64 — يتعامل مع الأوضاع القياسية والآمنة للروابط فوراً في متصفحك.

متاح أيضاً بـ: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.