moeru-ai/airi · warning

[CSV] Export is only supported in browser environments

Error message

[CSV] Export is only supported in browser environments

What it means

exportCsv implements the browser download flow: Blob + URL.createObjectURL + a synthetic anchor click. It feature-detects Blob, document, and URL first; in an environment missing any of them (Node, SSR/prerender, some workers) it warns and returns without producing a file.

Source

Thrown at packages/stage-shared/src/export-csv.ts:16

function quoteField(field: unknown): string {
  return `"${String(field).replace(/"/g, '""')}"`
}

function toCsv(rows: Array<Array<unknown>>): string {
  return rows
    .map(row => row.map(quoteField).join(','))
    .join('\n')
}

export function exportCsv(rows: Array<Array<unknown>>, basename: string) {
  if (!rows.length)
    return

  if (typeof Blob === 'undefined' || typeof document === 'undefined' || typeof URL === 'undefined') {
    console.warn('[CSV] Export is only supported in browser environments')
    return
  }

  const csv = toCsv(rows)
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = `${basename}-${Date.now()}.csv`
  link.click()
  URL.revokeObjectURL(url)
}

View on GitHub (pinned to 677329427f)

Solutions

  1. Only call exportCsv from browser/user-gesture code paths (click handlers in the renderer)
  2. For Node or Electron main, write the CSV with node:fs instead of the Blob download flow
  3. Feature-detect in the caller and hide the export button where unsupported, so users never trigger the silent no-op

Example fix

// before (shared code, runs everywhere)
exportCsv(rows, 'messages')

// after (branch by environment)
import { isBrowser } from './env'
if (isBrowser) {
  exportCsv(rows, 'messages')
}
else {
  await writeFile('messages.csv', toCsvText(rows), 'utf8')
}
Defensive patterns

Strategy: validation

Validate before calling

const canDownloadInBrowser = typeof Blob !== 'undefined' && typeof document !== 'undefined' && typeof URL !== 'undefined'
if (canDownloadInBrowser) {
  exportCsv(rows, 'messages')
}
else {
  console.warn('CSV download unavailable in this environment')
}

Type guard

function isBrowserDownloadEnv(): boolean {
  return typeof Blob !== 'undefined' && typeof document !== 'undefined' && typeof URL !== 'undefined'
}

Prevention

When it happens

Trigger: Calling exportCsv during SSR/prerender, in the Electron main process, in a Node script or test runner without DOM globals.

Common situations: A shared utility invoked from a server route during prerender; unit tests executing the module in pure Node; main-process export code reusing a shared helper.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/0ca8142b9d74fc65. Report an issue: GitHub.