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です。
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 />を呼び出すときに、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
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アイコンを表示しています。
この状態で動作確認をしてみます。
アイコンをクリックして、画像選択後、画像が画面に表示されることが確認できます。
4.Firebaseの設定
Firebaseを利用するにはgoogleアカウントが必要です。
googleアカウントでFirebaseにログインして、コンソールに移動します。
プロジェクト追加から新規プロジェクトを追加していきます。
google analyticsは不要な場合はチェックを外して、プロジェクトを作成します。
プロジェクト作成後、アプリを追加していきます。
アプリ名を適宜つけて、チェックマークを入れて登録します。
以下を実行します。
npm install firebase@9.*
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/
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> ); }
最後に、動作確認をしてみます。
consoleを見ると、firebaseのURLが取得できていることがわかります。
firebase storage側を見ると、imagesフォルダ内に送信した画像がuploadされていることが確認できます。
今回は以上となります。
参考
以下の記事にて、複数の画像をpreviewしてuploadする方法について解説しています。