🙌

PDF を Web API で Base64 転送して HTML に埋め込む

2022/09/18に公開

Web 系開発の話です。
バックエンドサーバ上にある PDF ファイル を Web API 経由でフロントエンドに転送して埋め込み表示する方法を試したため本稿にメモしておきます。

環境

  • next.js 12.3.0
  • react 18.2.0

構成

バックエンド側

  • サーバ側の PDF ファイルのバイナリを Base64 に変換して payload に乗せて送出します。
/pages/api/pdf.ts
import { readFile } from 'fs'
import type { NextApiRequest, NextApiResponse } from 'next'
import path from 'path'

type Data = {
  name: string
  data: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  readFile(path.join(process.cwd(), '/file/embed.pdf'), null, (err, data)=>{
    res.status(200).json({
      name: 'embed.pdf',
      data: data.toString('base64')
    })
  })
}

フロントエンド側

試して良さそうと思った方法。

  • GET リクエストで取得した payload から
    1. Data URL data:application/pdf;base64,(base64文字列) で直接埋め込み
    2. Blob 変換して Object URL を作成してから埋め込み

埋め込み先は Embed でも object でも iframe でも問題ありません。
詳しくは説明しませんが、データをダウンロードする場合は、直接 Base64 を埋め込むケースと Object URL を経由するケースでダウンロードファイル名の挙動が異なります。

/pages/index.tsx
import type { NextPage } from 'next';
import { useEffect, useRef } from 'react';
import styles from '../styles/Home.module.css';

const Home: NextPage = () => {
  const file = useRef<string>()
  const embed = useRef<HTMLEmbedElement>(null)
  const iframe = useRef<HTMLIFrameElement>(null)
  const open = useRef<HTMLAnchorElement>(null)
  const download = useRef<HTMLAnchorElement>(null)

  useEffect(()=>{
    fetch('/api/pdf').then(res=>res.json()).then(json=>{
      // バイナリを Embed する方法
      if(embed.current){
        embed.current.type = 'application/pdf'
        embed.current.src=`data:application/pdf;base64,${json.data}#toolbar=0&navpanes=0`
      }

      // Blob URL に変換して参照させる方法
      fetch(`data:application/pdf;base64,${json.data}`).then(res=>res.blob()).then(blob=>{
        const name = json.name
        file.current = URL.createObjectURL(blob)  // 一時 URL を作成

        if(iframe.current){
          iframe.current.src = `${file.current}#toolbar=0&navpanes=0`
        }
        if(open.current){
          open.current.href = file.current
        }
        if(download.current){
          download.current.href = file.current
          download.current.download = name  // ダウンロード指示 & ファイル名をサジェスト
        }
      })
    })

    // cleanup
    return ()=>{
      if(file.current) {
        URL.revokeObjectURL(file.current)  // URL を解放 (メモリリークも防止)
      }
    }
  },[])

  return (
    <main className={styles.main}>
      <div><embed ref={embed} width={800} height={800}/></div>
      <div><iframe ref={iframe} width={800} height={800}/></div>
      <div><a ref={open}>開く</a></div>
      <div><a ref={download}>ダウンロード</a></div>
    </main>
  )
}

export default Home

結果

Html に埋め込み表示できました。

ソースファイル

https://github.com/Fehde/http-base64-embed

Discussion