Closed4

Next.js + Typescript + FireStore + material-ui/styled-component + Vercel + SSGしたい

cryptoboxcryptobox

nextappの作成

npx create-next-app --ts
cd [app-name]
yarn dev

tsconfig.jsonの設定

tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

Material-UI と styled-componentsの導入

yarn add @material-ui/core @material-ui/styles styled-components @types/styled-components

src/theme.ts: theme の作成

// src/styles/theme.ts
import { createMuiTheme } from '@material-ui/core'
const theme = createMuiTheme()
export default theme

src/_app.tsx : ルートコンポーネント付近にテーマプロバイダーを設置

// src/pages/_app.tsx
import React, { useEffect } from 'react'

import { ThemeProvider as StyledComponentsThemeProvider } from 'styled-components'
import {
  ThemeProvider as MaterialUIThemeProvider,
  StylesProvider
} from '@material-ui/styles'
import CssBaseline from '@material-ui/core/CssBaseline'

import theme from '../styles/theme'
import { AppProps } from 'next/dist/next-server/lib/router/router'

const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
  // Remove the server-side injected CSS.(https://material-ui.com/guides/server-rendering/)
  useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side')
    if (jssStyles && jssStyles.parentNode) {
      jssStyles.parentNode.removeChild(jssStyles)
    }
  }, [])

  return (
    <StylesProvider injectFirst>
      <MaterialUIThemeProvider theme={theme}>
        <StyledComponentsThemeProvider theme={theme}>
          <CssBaseline />
          <Component {...pageProps} />
        </StyledComponentsThemeProvider>
      </MaterialUIThemeProvider>
    </StylesProvider>
  )
}

export default MyApp

pages/_document.tsxをつくる

// src/pages/_documents.tsx
import React from 'react'
import NextDocument, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
  DocumentInitialProps
} from 'next/document'
import { RenderPageResult } from 'next/dist/next-server/lib/utils'
import { ServerStyleSheet } from 'styled-components'
import { ServerStyleSheets as MaterialServerStyleSheets } from '@material-ui/core'

export default class CustomDocument extends NextDocument {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const styledComponentsSheet = new ServerStyleSheet()
    const materialUiSheets = new MaterialServerStyleSheets()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = (): RenderPageResult | Promise<RenderPageResult> =>
        originalRenderPage({
          enhanceApp: (App) => (
            props
          ): React.ReactElement<{
            sheet: ServerStyleSheet
          }> =>
            styledComponentsSheet.collectStyles(
              materialUiSheets.collect(<App {...props} />)
            )
        })

      const initialProps = await NextDocument.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: [
          <React.Fragment key="styles">
            {initialProps.styles}
            {styledComponentsSheet.getStyleElement()}
            {materialUiSheets.getStyleElement()}
          </React.Fragment>
        ]
      }
    } finally {
      styledComponentsSheet.seal()
    }
  }

  render(): React.ReactElement {
    return (
      <Html lang="ja-JP">
        <Head>
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

src/components/StyledComponents.tsx: Material-UI のコンポーネントを styled-components で装飾する。

import React from 'react';
import styled from 'styled-components';
import { Button } from '@material-ui/core';

// Material-UI をカッコで囲んで、styled の引数にしてやる
const StyledButton = styled(Button)`
  background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
  border-radius: 3px;
  border: 0;
  color: white;
  height: 48px;
  padding: 0 30px;
  box-shadow: 0 3px 5px 2px rgba(255, 105, 135, .3);
`;

export default function StyledComponents() {
  return (
    <div>
      <Button>Material-UI</Button>
      <StyledButton>Styled Components</StyledButton>
    </div>
  );
}
cryptoboxcryptobox

firebaseの導入

  1. firebaseコンソールからプロジェクトを作成する
  2. firebase init
  3. cloudfunctionsとfirestoreを選択してインストール
  4. firebaseコンソールから作成したプロジェクトの設定へ飛び、サービスアカウントタブを開く
  5. 「新しい秘密鍵の生成」を押下し秘密鍵を生成する
  6. .env ファイルを作成し以下を設定(設定は生成した秘密鍵のjsonファイルに書いてある)
.env
NEXT_PUBLIC_FIREBASE_PROJECT_ID="***************"
NEXT_PUBLIC_FIREBASE_CLIENT_EMAIL="***************"
NEXT_PUBLIC_FIREBASE_PRIVATE_KEY="***************"
  1. firebase-adminの初期化
lib/db.js
import * as admin from 'firebase-admin';

if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
      clientEmail: process.env.NEXT_PUBLIC_FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.NEXT_PUBLIC_FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
    }),
  });
}

export const db = admin.firestore();
  1. firestoreにデータを登録
  2. pages ディレクトリに以下を記述
pages/firebase.js
import { db } from '../lib/db';
import Link from 'next/link';

export default function Firebase({ tasks }) {
  return (
    <>
      <h1>Firebaseのページ</h1>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
      <Link href={`/`}>
        <a>戻る</a>
      </Link>
    </>
  );
}

export async function getStaticProps() {
  const tasks = [];
  const ref = await db.collection('tasks').get();
  ref.docs.map((doc) => {
    const data = { id: doc.id, title: doc.data().title };
    tasks.push(data);
  });
  return {
    props: {
      tasks,
    },
  };
}
  1. yarn dev してデータを取得できているか確認
cryptoboxcryptobox

Vercelでデプロイ

  1. プロジェクトをgit commitしてpush
  2. Vercelにgithubでログイン
  3. プロジェクトを選択して setting から Enviroment Variables へ行き環境変数を設定する。環境変数はfirebaseコンソールのプロジェクト設定 > 全般にある。privateKeyとclientEmailは秘密鍵のjsonに書いてある。
NEXT_PUBLIC_FIREBASE_API_KEY=************
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=************
NEXT_PUBLIC_FIREBASE_DETABASE_URL=************
NEXT_PUBLIC_FIREBASE_PROJECT_ID=************
NEXT_PUBLIC_FIREBASE_STORAGE_BAKET=************
NEXT_PUBLIC_FIREBASE_MESSAGE_SENDER_ID=************
NEXT_PUBLIC_FIREBASE_APP_ID=************
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=************
NEXT_PUBLIC_FIREBASE_CLIENT_EMAIL=************
NEXT_PUBLIC_FIREBASE_PRIVATE_KEY=************
このスクラップは2021/06/27にクローズされました