LoginSignup
4
3

More than 1 year has passed since last update.

【JAMstack】Next.js × Firebase で JAMstack なブログサイトを作る④[管理アプリ]

Last updated at Posted at 2021-07-09

概要

本記事は、【JAMstack】Next.js × Firebase で JAMstack なブログサイトを作る③[Hooks]の続きとなっています。

本記事では、Firebase Storageへ記事の投稿・削除を行うアプリを作成します。
JAMstackブログサービスの基幹部分は前回までで完成しましたが、記事の投稿・削除はFirebase Storage上でしかできないので、ブログの管理者用のアプリを作成します。

プロジェクトの作成

Firebase Hosting

Firebase Hostingのプロジェクトを作成しておきます。
※ Firebase自体のプロジェクトは、【JAMstack】Next.js × Firebase で JAMstack なブログサイトを作る②[ブログサイト]で作成したものを使います。

アプリケーション

アプリケーションは、RCA(create-react-app)で作成します。
作業フォルダを作成して、そこでコマンドプロンプトで以下を実行します。

npx create-react-app . --template typescript

以下を実行して動作確認をします。

npm run start

Hosting テスト

アプリケーションを作成したら、Hostingができるか確認しておきます。
firebase-toolsをインストールしていない場合は、インストールします。

npm i -g firebase-tools

プロジェクトで、コマンドプロンプトを使用して以下を実行します。

firebase init

初期設定は、以下のようにします。

  • Hosting(上)を選択します。
  • 既存プロジェクトとして、Firebaseのプロジェクトを選択します。
  • デプロイするフォルダ名をbuildにします。
  • React(SPA)なので、single-page-appをYesにします。
  • GitHubと連携してリビルドする設定はNoにしておきます。(お好み)

無題6.png

初期設定ができたら、以下を実行してHostingします。

npm run deploy

成功すればURLが表示されるので、アクセスしてReactの初期ページが表示されればOKです。

アプリケーションの作成

機能

メインの機能として、以下のことをできるようにします。

  • Firebase Storageにブログ記事を投稿する
  • Firebase Storage内のブログ記事を削除する
  • 削除するために、Firebase Storage内のブログ記事を表示する

オプションの機能として、以下も実装します。

  • Firebase Authenticationを使用して認証機能を実装する
  • ブログ記事で画像を扱えるようにするために、画像投稿機能を実装する
  • 画像投稿機能を実装する上で、削除、一覧の表示機能も実装する

使用するライブラリ

UIは、Material-UIを使用します。

状態管理は、Recoilを使用します。(使うのは認証部分のみ)

スタイリングは、Material-UIのコンポーネントはMaterial-UIで用意されているStyle方法、通常のコンポーネントについてはemotion/cssで行います。

パッケージ インストール

npm i @material-ui/core @material-ui/icons
npm i recoil @types/recoil
npm i @emotion/css
npm i gray-matter

gray-matterは、取得した記事をテキストデータからオブジェクトデータに変換するために使用します。

Firebase Storage のアクセス設定

Rules

Firebase Storageの設定で、Rulesを変更します。
無題9.png

CORS

アプリケーション内で記事や画像を取得する場合、StorageにCORSを設定する必要があります。
設定方法は以下を確認してください。

外部ユーザーの Storage アクセス権限

画像をブログ記事に埋め込んで表示するためには、Storageを外部に公開する必要があります。
特殊なメンバーallUsersを追加して、Storageの読み取り専用の権限を与えます。

コーディングについて

コーディング部分は、長くなるので割愛します。成果物のGitHubリンクを参照してください。

環境変数の設定

RCA(create-react-app)で作成されたプロジェクトでは、特になにかをインストールしなくても環境変数を扱えます。また、環境変数の接頭辞はREACT_APP_にする必要があります。
root直下に.env.localを作成して、FirebaseのSDK の設定と構成で取得できる値を以下のように設定します。

.env.local
REACT_APP_FIREBASE_APIKEY="apiKey"
REACT_APP_FIREBASE_DOMAIN="authDomain"
REACT_APP_FIREBASE_DATABASE="https://PROJECT_ID.firebaseio.com"
REACT_APP_FIREBASE_PROJECT_ID="projectId"
REACT_APP_FIREBASE_STORAGE_BUCKET="storageBucket"
REACT_APP_FIREBASE_SENDER_ID="messagingSenderId"
REACT_APP_FIREBASE_APP_ID="appId"

以下のファイルを作成して、firebaseの初期化設定を行う処理を記述します。

src/firebase/firebase.ts
import 'firebase/auth';
import 'firebase/storage';
import firebase from 'firebase/app';

const firebaseConfig = {
    apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
    projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
    storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_FIREBASE_APP_ID
};

if (!firebase.apps.length) firebase.initializeApp(firebaseConfig);

export const auth = firebase.auth();
export const storage = firebase.storage();
export const provider = new firebase.auth.GoogleAuthProvider();

認証では、メール&パスワードの他にGoogleアカウント認証もできるようにしました。

SingIn 状態の監視

Firebase Authenticationには、ユーザーのSingnIn状態を監視する関数があります。
これを状態管理ライブラリ(Recoil)と組み合わせることで、SignIn機能をスムーズに実装できます。

src/firebase/authFunctions.ts
/**
 * SignInの状態を監視する
 */
export const useAuth = () => {
    const [signInUser, setSignInUser] = useRecoilState(signInUserState);
    const resetStatus = useResetRecoilState(signInUserState);

    useEffect(() => {
        const unSub = auth.onAuthStateChanged(authUser => {
            if (authUser) {
                setSignInUser({
                    uid: authUser.uid,
                    email: authUser.email!
                });
            } else {
                resetStatus();
            }
        });
        return () => unSub();
    }, [setSignInUser, resetStatus]);

    return signInUser;
};

記事の取得

記事は、getDownloadURLを使用してurlを取得して、fetchすることで取得できます。
それをgray-matterを使用して解析して、必要な情報を抜き出します。

src/firebase/storageFunctions.ts
const getArticleContents = async (article: any) => {
    const url = await article.getDownloadURL();
    const res = await fetch(url);
    const data = await res.text();
    const matterResult = matter(data);
    return {
        path: article.fullPath as string,
        ...(matterResult.data as { date: string; title: string })
    };
};

※ 引数の型anyは、本来firebase.storage.Referenceですが、正常に設定できなかったため泣く泣くanyにしています。

画像の取得

Firestore Storageを外部公開(読み取りのみ)しているので、画像はそれを利用してurlを取得し、imgタグに割り当てます。

src/firebase/storageFunctions.ts
const urlPrefix = `https://storage.googleapis.com/${process.env.REACT_APP_FIREBASE_STORAGE_BUCKET}/`;

/**
 * 画像データ配列を取得する
 * @returns 日付ソートした画像データ配列
 */
export const getImages = async () => {
    try {
        const result: ImageType[] = [];
        const images = await storage.ref('images').list();
        await Promise.all(
            images.items.map(async item => {
                const meta = await item.getMetadata();

                if ((meta.contentType as string).startsWith('image')) {
                    result.push({
                        name: meta.name,
                        date: new Date(meta.updated).toLocaleString(),
                        url: urlPrefix + (meta.fullPath as string)
                    });
                }
            })
        );

        return result.sort((a: ImageType, b: ImageType) => {
            const a_PathMilli = new Date(a.date);
            const b_PathMilli = new Date(b.date);
            return a_PathMilli < b_PathMilli ? 1 : -1;
        });
    } catch (error) {
        console.log({ error });
    }
};

その他の処理

ファイルのアップロードや削除は、ドキュメントを読めば特に詰まることなく実装できると思います。

動作確認

管理アプリからブログ記事の投稿、削除を行うと、VercelのDashboardで自動的にリビルド・デプロイされ、ブログサイトが更新されることを確認します。
また、アップロードした画像リンクをブログ記事に埋め込むと、画像が表示されることを確認します。

最後にFirebase Hostingにデプロイして終了です。

成果物

SignIn画面

無題7.png

管理画面

無題8.png

まとめ

ようやくJAMstackなブログサイトが完成しました。
Firebaseを使えば簡単にできるかなーって始めましたが、思ったより時間と労力がかかりました。調査7割、実装3割くらいの労力でした。
でも、調べて解決する分だけ、できることの幅が広がると思えば報われます。😌

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3