kewpie134134
@kewpie134134

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Next.js で Firebase Storage に画像をアップロードする方法を教えて下さい

解決したいこと

React と Next.js、Typsscript について勉強しているのですが、エラーが解決できないので質問しました。
Next.js で、Firebase Storage に画像をアップロードして、その画像を Next.js 側で表示するという Web アプリを作っています。
画像をアップロードする際に、Next.js のpages/apiからアップロードを試みているのですが、エラーが発生してしまい Firebase Storage にアップロードできません。
なお、画像ファイルは base64 でアップロードしようと考えていますが、現状できていません。
Next.js で Firebase Storage に画像をアップロードする方法、もしくは Next.js で画像をアップロードするより良い方法などあれば教えてください。

主なディレクトリ構成

関係がありそうなディレクトリ構成は以下になります。
pages/index.tsx で画像ファイルをインプットで選択させようとしています。
画像のアップロード関連の処理は、components/Upload.tsx で行っています。
そして、実際に Firebase Storage に画像を送信する処理は、components/Upload.tsxからpages/api/post/[date].tsを使用してアップロードしようと考えています。

root
├ components
│  └ Upload.tsx
├ firebase
│  └ firebase.ts
└ pages
    ├ api
    │  └ post
    │     └ [date].js
    └ index.tsx

Base64 で画像をアップロードする場合

Base64 形式でアップロードしようとする場合に発生している問題・エラー

ブラウザ側で以下のようなエラーが表示されます。

Upload.tsx:45 POST http://localhost:3000/api/post/2020-04-03 500 (Internal Server Error)
Upload.tsx:55 {statusCode: 500, message: "Firebase Storage: String does not match format 'ba… Invalid character found (storage/invalid-format)"}message: "Firebase Storage: String does not match format 'base64': Invalid character found (storage/invalid-format)"statusCode: 500__proto__: Object

Base64 形式でアップロードしようとする場合の該当するソースコード

  • pages/index.tsx
import React from "react";
import Link from "next/link";
import Layout from "../components/Layout";
import Upload from "../components/Upload";

import { GetStaticProps } from "next";
import { db } from "../firebase/firebase";

type ItemDocs = {
  date: string;
};

const IndexPage = () => (
  <Layout title="画像アップローダー">
    <h1>画像アップローダー</h1>
    <Upload />
    <p>
      <Link href="/picture-book">
        <a>写真館</a>
      </Link>
    </p>
  </Layout>
);
  • Components/Upload.tsx

ImageRender(imageRender)部分で、pages/api/post/[date].jsを呼んでいます。

import React, { useState } from "react";
import Image from "next/image";

type ImageData = {
  imageData: string;
};

const Upload = () => {
  // 選択された画像データを保持しておくState
  const [imageData, setImageData] = useState<string>("");

  // 「ファイルを選択」ボタンが押されたら発火する
  const handleImageChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
    // 選択された画像の情報が格納される
    const files = event.target.files;
    // ファイルが読み込まれたら読み込み作業を実施
    if (files && files.length > 0) {
      const file = files[0];
      // 画像読み込みリーダーのインスタンス生成
      const reader = new FileReader();
      reader.onload = (event) => {
        if (event.target && event.target.result)
          setImageData(event.target?.result.toString());
      };
      // HTML 側で渡された画像を読み込む
      reader.readAsDataURL(file);
    } else {
      setImageData("");
    }
  };

  // 選択した画像を表示させるためのプレビュー用コンポーネント
  const ImageRender = (imageData: ImageData) => {
    // imageDate のオブジェクトに base64 形式のデータが入っているか確認
    if (Object.values(imageData)[0] === "") return null;
    // base64 データがあれば、表示させる。
    else {
      // 画像をアップロードする関数
      const onUploadImage = async () => {
        // firestore, storage for firebase に API を経由して情報を登録

        // ★★★★ここからがアップロード処理部分★★★★
        const res = await fetch("api/post/2020-04-03", {
          body: JSON.stringify({
            filename: Object.values(imageData)[0],
          }),
          headers: {
            "Content-Type": "application/json",
          },
          method: "POST",
        });
        const result = await res.json();
        console.log(result);
        console.log("あっぷろーど!");
      };
      return (
        <>
          <Image src={imageData.imageData} width={200} height={200} />
          <div>
            <button onClick={onUploadImage}>アップロード!</button>
          </div>
        </>
      );
    }
  };

  // メインレンダー部分
  return (
    <>
      <input
        type="file"
        accept="image/jpeg, image/png"
        onChange={(event) => handleImageChanged(event)}
      />
      <div>
        <ImageRender imageData={imageData} />
      </div>
    </>
  );
};

export default Upload;
  • pages/api/post/[date].ts

こちらでは、Firestore にも別データを保存しようとしており、Firestore へのデータ登録は正常に行われています。
また、firebase.storage().child('images')のについても取得出来ていることから、firebase への接続、また storage への接続は行えていると考えています。

import { NextApiRequest, NextApiResponse } from "next";

import { db, storage } from "../../../firebase/firebase";

// Upload.tsx コンポーネントから、firebase の firestore にアップロードする API
const handler = (req: NextApiRequest, res: NextApiResponse) => {
  // /api/post/[date] で date を受け取り、リクエストに設定する
  const {
    query: { date },
  } = req;
  try {
    // storage for firebase に画像をアップロードする
    const [imageType, rawBase64Data] = req.body.filename.split(",");
    console.log(imageType); // -> data:image/png;base64
    console.log(rawBase64Data.substr(0, 50)); // -> iVBORw0KGgoAAAANSUhEUgAAAhUAAAIYCAYAAADaeh6gAAAABH

    // ★★★★ ここでfirebase storage に画像データを送信 ★★★★
    storage
      .ref()
      .child("images/2021-03-23.jpg")
      .putString(rawBase64Data, "base64")
      .then((_snapshot) => {
        console.log("Uploaded a data_url string!");
      });

    // firestore の images というコレクションにデータをアップロード
    db.collection("images").doc(`${date}`).set({
      path: "qwerqwer",
    });
  } catch (err) {
    res.status(500).json({ statusCode: 500, message: err.message });
  }
};

export default handler;

自分で試したこと

まずは Firebase のドキュメントを見ながら行ったのですが、うまくアップロードすることができませんでした。

Firebase -- ファイルをアップロードする

また、エラーメッセージが以下のように表示されていたため、putString()の引数に入れる値を、データのみにしてみたのですが、それでもうまくアップロードすることはできませんでした。

  • (旧):data:image/png;base64, iVBORw0KGgoAAAANSUhEU ... gU==
  • (新):iVBORw0KGgoAAAANSUhEU ... gU==
"Firebase Storage: String does not match format 'base64': Invalid character found (storage/invalid-format)"

Blob で行ってみようと思って Components/Upload.tsxpages/api/post/[date].tsの一部を以下のように変えて試してみたのですが、こちらもうまくいきませんでした。

  • Components/Upload.tsx
import React, { useState } from 'react';

...

const Upload = () => {
  // 選択された画像データを保持しておくState
  const [preview, setPreview] = useState('');
  const [imageData, setImageData] = useState<File | null>(null);

  // 「ファイルを選択」ボタンが押されたら発火する
  const handleChangeFile = (event: React.ChangeEvent<HTMLInputElement>) => {
    const { files } = event.target;
    if (files) {
      setPreview(window.URL.createObjectURL(files[0]));
      setImageData(files[0]);
    }
  };

  // 選択した画像を表示させるためのプレビュー用コンポーネント
  const ImageRender = (preview: PreviewProps) => {
    // imageDate のオブジェクトにデータが入っているか確認
    if (Object.values(preview)[ZERO] === EMPTY_STRING) return null;
    // データがあれば、表示させる。
    else {
      // 画像をアップロードする関数
      const onUploadImage = async () => {
        const response = await fetch(preview.preview);
        const blob = await response.blob();

        // firestore, storage for firebase に API を経由して情報を登録
        const res = await fetch('api/post/2020-04-03', {
          body: blob,
          headers: {
            'Content-Type': 'image/png',
          },
          method: 'POST',
        });
        // const result = await res.json();
        // console.log(result);
        console.log('あっぷろーど!');
      };
      return (
        <>
          <img src={preview.preview} />
          <div>
            <button onClick={onUploadImage}>アップロード!</button>
          </div>
        </>
      );
    }
  };

...

export default Upload;
  • pages/api/post/[date].ts
import { NextApiRequest, NextApiResponse } from 'next';

...
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  // /api/post/[date] で date を受け取り、リクエストに設定する
  const {
    query: { date },
  } = req;
  try {
    storage
      .ref()
      .child('images/2021-03-23.jpg')
      // .putString(rawBase64Data, 'base64')
      .put(req.body, { contentType: 'image/png' })
      .then((_snapshot) => {
        console.log('Uploaded a data_url string!');
      });

      ...

  } catch (err) {
    res.status(500).json({ statusCode: 500, message: err.message });
  }
};

...

export default handler;


1

No Answers yet.

Your answer might help someone💌