Next.jsとFirebase storageで画像をuploadする方法

JS

Next.jsとFirebaseを組み合わせると、簡単に画像ファイルのアップロードを行うことができます。

Next.js はReactのframeworkになります。

画像ファイルのstorage(保存先)として、Firebase storageを利用します。

またFirebaseのSparkプランは無料で利用することができます。

1.Next.js プロジェクト作成

空のプロジェクトを適宜作成します。

空のフォルダに移動して、cmd/terminalから以下のコマンドを実行して、Next.jsプロジェクトを作成します。

npx create-next-app . --use-npm

以下のコマンドでtailwindcssをインストールします。

npm install tailwindcss postcss@latest autoprefixer@latest
npx tailwindcss init -p

※本記事では、スタイルの解説はしておりません。

heroiconを使用するので以下のコマンドを実行します。

npm install @heroicons/react

tailwind.config.jsの編集

module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [ ],
}

 

pages/_app.js

import 'tailwindcss/tailwind.css'

 

この状態で一度以下を実行して、localhostを立ち上げてみます。

npm run dev

http://localhost:3000

welcomeページが表示されれば、OKです。

nextjs-welcome

componentsフォルダ作成とLayout.jsの作成。

components/Layout.js

export default function Layout({ children, title = 'default title'}) {
  return (
    <div className='flex justify-center items-center flex-col min-h-screen text-white font-mono bg-gray-800'>
      <Head>
        <title>{title}</title>
      </Head>
      <main className='flex flex-1 justify-center items-center w-screen flex-col'>
        {children}
      </main>
      <footer className='mb-4 w-full h-6 flex justify-center items-center text-gray-500 text-sm'>
        @M-yoshimura-ML 2022
      </footer>
    </div>
  )
}

Layout-js

こちらのLayout.jsを他のページで呼び出すことで、レイアウトが統一されます。

別ページで<Layout />を呼び出すときに、titleの値を指定するとそのページが表示されたときのタイトルも変更されます。

またfooter情報も自動的に表示されます。(footerの記載は適宜変更下さい)

2.upload用画面作成

pages配下にupload-file.jsを新規作成します。

pages/upload-file.js

import { useState } from "react";
import Layout from '../components/Layout';


export default function UploadImage() {

  return (
    <Layout title="upload image check">
      <div className="mb-4">this is upload image screen</div>

      <label htmlFor="file-input" className="bg-primary-900 text-white-900 dark:bg-dark-900 flex justify-center items-center px-4 py-2 rounded mb-6 w-full" >
      <svg xmlns="http://www.w3.org/2000/svg"
        className="h-10 w-10 hover:cursor-pointer hover:bg-gray-700"
          fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"
      >
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
        </svg>
      </label>
    </Layout>
  );
}

react hookのuseStateはファイルの状態管理のため、後ほど使用します。

Layoutコンポーネントを読み込み、その中で<label>タグとphotographというheroiconを<svg>タグで読み込んでいます。

この状態で以下にアクセスしてみます。

http://localhost:3000/upload-file

upload-page

Layoutコンポーネントの内容と、photographアイコンが表示されればOKです。

3.画像のpreview機能作成

以下をpages/upload-file.jsのfunction内に追加します。

    const [image, setImage] = useState(null);
    const [createObjectURL, setCreateObjectURL] = useState(null);

1つ目はimageのオブジェクトを管理して、後にFirebase storageへ送るために用意します。

2つ目のcreateObjectURLは、画像を選択して、画面にpreview用として表示するためのURL格納用になります。

次に以下の処理を追加します。

    const uploadToClient = (event) => {
        if (event.target.files && event.target.files[0]) {
        const file = event.target.files[0];
 
        setImage(file);
        setCreateObjectURL(URL.createObjectURL(file));
        }
    };

こちらの処理は、画像が選択された後に実行するようにして、fileオブジェクトをimageにセットします。

またcreateObjectURLメソッドでfileのURLを生成して、createObjectURLにセットしています。

以下の<img>タグを<label>タグの上に追加します。

  <img className="flex justify-center items-center" src={createObjectURL} />

photographアイコンを押したときに、画像選択ができるように以下の<input>タグを<label>タグの下に追加します。

<input id="file-input" className="hidden" type="file" accept="image/*" name="myImage" onChange={uploadToClient} />

<label>のhtmlFor=”file-input”と<input>のid=”file-input”で紐づけをしています。

onChangeで状態が変わった時(ファイル選択された後)、uploadToClientを実行するようにしています。

<label>を使わずに、<input>タグでもfile自体はpreview, upload可能ですが、<input>だとださいので、hidden属性で表示しないようにして、代わりにphotographアイコンを表示しています。

この状態で動作確認をしてみます。

image-preview

アイコンをクリックして、画像選択後、画像が画面に表示されることが確認できます。

4.Firebaseの設定

Firebaseを利用するにはgoogleアカウントが必要です。

googleアカウントでFirebaseにログインして、コンソールに移動します。

firebase-console

プロジェクト追加から新規プロジェクトを追加していきます。

firebase-project

google analyticsは不要な場合はチェックを外して、プロジェクトを作成します。

firebase-project-spark-plan

プロジェクト作成後、アプリを追加していきます。

firebase-add-web-app

アプリ名を適宜つけて、チェックマークを入れて登録します。

以下を実行します。

npm install firebase@9.*

firebase-add-web-app-sdk

SDKの追加ページで、表示された内容をコピーします。

helpersフォルダをディレクトリ直下に作成して、その中にfirebase.jsを作成します。
firebase.jsにコピーした内容を張り付けます。

※別の階層にfirebase.jsを作る場合は、以降フォルダの相対パスに注意

張り付けた後に、値を直接記述するのではなく、.envを参照するようにconfigに記載を変更します。

helpers/firebase.js

import { initializeApp } from "firebase/app";
import { getStorage } from "firebase/storage";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
 
const app = initializeApp(firebaseConfig);
export const storage = getStorage(app);
 
console.log(app);

envの設定を読み取れるようにするには、Nexjsの場合、先頭にNEX_PUBLIC_ を付けます。export const storage で他のファイルでもstorageが使えるようにします。

console.log(app)で初期化がちゃんと読み込まれているかをconsole上で確認します。

プロジェクトフォルダ直下に、.envファイルを作成して、以下のように記述します。

NEXT_PUBLIC_FIREBASE_API_KEY="[your credential key]"

この状態で、localhostにアクセスします。

またchromeなどのconsoleを開いておきます。

http://localhost:3000/

firebase-setting-console-check

console上で_optionsに各設定したkeyとvalueが表示されれば、読み込みはOKです。

firebase画面上に表示されているCLIのインストールを実行しておきます。

npm install -g firebase-tools

ログインコマンドは適宜メモに控えておけばOKです。

最後に、左のメニューにあるStorageから、storageを作成しておけば、firebase側の準備は完了です。

5.Firebase storageへファイル送信

firebaseへファイル送信用の関数を作成します。

pages/api/upload.js

import { storage } from "../../helpers/firebase";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
 
 
//single image file upload
export const postImage = async(image=null) => {
    let uploadResult = '';
 
    if(image.name){
        const storageRef = ref(storage);
        const ext = image.name.split('.').pop();
        const hashName = Math.random().toString(36).slice(-8);
        const fullPath = '/images/' + hashName + '.' + ext;
        const uploadRef = ref(storageRef, fullPath);
 
        // 'file' comes from the Blob or File API
        await uploadBytes(uploadRef, image).then(async function(result) {
            console.log(result);
            console.log('Uploaded a blob or file!');
 
            await getDownloadURL(uploadRef).then(function(url){
                uploadResult = url;
            });
        });
    }
    return uploadResult;
}

引数にimageオブジェクトを指定しておきます。

uploadBytesでfirebase storageに送信しますが、時間がかかるので、async awaitで非同期処理にしています。

fileをアップロードした後、その画像を他の画面で使用するには、画像のURLを取得する必要があります。

getDownloadURLで画像のパス(URL)を取得します。ブラウザでは、URLが取得できているかを確認します。

ファイル名をランダムな文字列に変更していますが、しなくてもOKです。
その場合は、image.nameをfullPathに渡します。

 

upload-file.jsを変更していきます。

pages/upload-file.js

import { postImage } from "./api/upload";

postImage(storageへの送信処理)を読み込めるようにimportを追加します。

以下の処理を追加します。

   const uploadToServer = async () => {
        const result = await postImage(image);
        console.log(result);
    };

送信用のボタンを押したときに、この処理を実行するようにします。

引数imageは、useStateで状態管理されているimageが渡されます。

送信用のボタンを<input>タグの下に追加します。

<button className="btn btn-primary" type="submit" onClick={uploadToServer}>
  Send to server
</button>

全体のupload-file.jsは以下のようになります。

import { useState } from "react";
import Layout from '../components/Layout';
import { postImage } from "./api/upload";
 
 
export default function UploadImage() {
    const [image, setImage] = useState(null);
    const [createObjectURL, setCreateObjectURL] = useState(null);
 
 
    const uploadToClient = (event) => {
        if (event.target.files && event.target.files[0]) {
        const file = event.target.files[0];
 
        setImage(file);
        setCreateObjectURL(URL.createObjectURL(file));
        }
    };
 
    const uploadToServer = async () => {
        const result = await postImage(image);
        console.log(result);
    };

    return (
      <Layout title="upload image">
        <div className="mb-4">this is upload image screen</div>
        <img className="flex justify-center items-center" src={createObjectURL} />
        <label htmlFor="file-input" className="bg-primary-900 text-white-900 dark:bg-dark-900 flex justify-center items-center px-4 py-2 rounded mb-6 w-full" >
          <svg xmlns="http://www.w3.org/2000/svg"
            className="h-10 w-10 hover:cursor-pointer hover:bg-gray-700"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
            strokeWidth="2"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
          </svg>
        </label>
        <input id="file-input" className="hidden" type="file" accept="image/*" name="myImage" onChange={uploadToClient} />
        <button className="btn btn-primary" type="submit" onClick={uploadToServer}>
          Send to server
        </button>
      </Layout>
    );
}

最後に、動作確認をしてみます。

success-file-upload-to-storage

consoleを見ると、firebaseのURLが取得できていることがわかります。

firebase-storage-file-uploaded

firebase storage側を見ると、imagesフォルダ内に送信した画像がuploadされていることが確認できます。

 

今回は以上となります。

参考

 

以下の記事にて、複数の画像をpreviewしてuploadする方法について解説しています。

Next.jsで複数画像をpreview表示してuploadする方法
本記事では、ファイルのstorage(保存先)として、Firebase storageを利用します。今回、複数のファイル(画像)をまず画面にpreview表示して、表示した複数の画像をFirebase storageへuploadする方法を紹介致します。