ジャバ・ザ・ハットリ

ベルリンのITスタートアップで働くソフトウェアエンジニア

Gatsbyにログイン機能をつけてみよう

2020-03-31Gatsby

Loading...

Gatsbyは実はカンタンにログイン機能をつけることができる。ユーザーがシステムにログインして固有の情報を見せる機能。例えばジャスティン氏がログインしたら「ジャスティン・ビーバーさん、今日もあたしのGatsbyにログインしてくれてありがとう(ハート)」みたいなメッセージを出すとか。

Gatsbyの爆速に惚れて「なんかGatsbyで作ろうかな」となったとする。用途はブログでも創作物の作品集でもランディングページでもいい。「ログイン機能を付けたいんだけどなー。でもこれは『静的サイト』だろ。無理かな?」と考えてしまっていたら「それカンタンにできますよ」と言いたい。フルカスタマイズが可能なGatsbyの場合、だいたいなんでもできる。

もろろん会員情報を入れておくためのサーバーとしてFirebaseやRailsが必要にはなる。表側の爆速処理は全部Gatsbyにやってもらって、裏側の会員情報やらをサーバーにやってもらう、となる。

ブログのようにほとんどの情報出力が静的な内容で、たまーにユーザーログインが要る、ってぐらいならこれで十分。

Gatsbyには豊富はプラグインがあるので、だいたいの機能追加にはプラグインをインストールすれば1発で終わる。でもこのログイン機能にはプラグインが無い。それはサーバー側の構成によって変わるし、プラグインの作りようが無いからだと思う。

ただそんなに難しくもないのでこの解説を読んで試してみてはいかがでしょうか?

ここではサーバー側は無しであくまでGatsbyの設定だけを書いた。

この解説で完成するログイン機能ありのブログはこちら。https://jabba14.gitlab.io/gatsby_login/

image

ソースコードはこちら。https://gitlab.com/Jabba14/gatsby_login

ネット上の技術解説記事でレポジトリーが公開されてないと「この記事の通りにやったら本当にこれは動くんかいな」と不安になりますよね。そういう不安ある方はこちらからお試しください。

公式サイトの解説はこちら(このブログ記事では内容をちょっと変えた)

authentication-tutorial

スターターキットから開始する

こちらのGatsbyをはじめる手順にしたがって、まずはスターターキットから選ぶ。

今回は公式スターターキットであるgatsby-starter-blogにした。やっぱり公式の方が手堅い。

このコマンドで入れる。

$ gatsby new gatsby_login https://github.com/gatsbyjs/gatsby-starter-blog

Gatsbyブログラムが走ってインストールされる。

完了したら、フォルダにいって立ち上げる。

$ cd gatsby_login
$ gatsby develop

ブラウザで http://localhost:8000/ にアクセスするとこんなブログができている。

image

確認できたらここまでをgitに残しておく。

$ git initial
$ git add .
$ git commit -m ‘initial’

現在のフォルダ構成はこうなっているはず。

▸ content/
▸ node_modules/
▸ public/
▸ src/
▸ static/
  gatsby-browser.js
  gatsby-config.js
  gatsby-node.js
  LICENSE
  package-lock.json
  package.json
  README.md
  yarn.lock

今回、主に変更するのはsrc/配下のファイルになる。

ナビテーションバーを作る

ナビテーションバーで今のログイン状態やログアウトリンクを表示させるのに使う。そんな機能はcomponentsに入れる。

src/ 配下のフォルダ構成はこうなっている。

▾ src/
  ▸ components/
  ▸ pages/
  ▸ templates/
  ▸ utils/

components/の配下にnav-bar.jsを作る。

// src/components/nav-bar.js

import React from "react"
import { Link } from "gatsby"

export default () => (
  <div
    style={{
      display: "flex",
      flex: "1",
      justifyContent: "space-between",
      borderBottom: "1px solid #d1c1e0",
    }}
  >
    <span>You are not logged in</span>

    <nav>
      <Link to="/">Home</Link>
      {` `}
      <Link to="/">Profile</Link>
      {` `}
      <Link to="/">Logout</Link>
    </nav>
  </div>
)

とりあえずHome、Profile、Logoutを表示するだけにしておく。

作ったnav-barをレイアウトの中に入れる。既存のファイルに加えたのは import<NavBar /> のみ。

// src/components/layout.js

import React from "react"
import { Link } from "gatsby"

import { rhythm, scale } from "../utils/typography"
import NavBar from "./nav-bar"

const Layout = ({ location, title, children }) => {
  const rootPath = `${__PATH_PREFIX__}/`
  let header

  if (location.pathname === rootPath) {
    header = (
      <>
        <NavBar />
        <h1
          style={{
            ...scale(1.5),
            marginBottom: rhythm(1.5),
            marginTop: 0,
          }}
        >
          <Link
            style={{
              boxShadow: `none`,
              color: `inherit`,
            }}
            to={`/`}
          >
            {title}
          </Link>
        </h1>
      </>
    )
  } else {
    header = (
      <>
        <NavBar />
        <h3
          style={{
            fontFamily: `Montserrat, sans-serif`,
            marginTop: 0,
          }}
        >
          <Link
            style={{
              boxShadow: `none`,
              color: `inherit`,
            }}
            to={`/`}
          >
            {title}
          </Link>
        </h3>
      </>
    )
  }
     // 以下変更無し

この時点で以下のようなナビゲーションバーがヘッダーに表示されているはず。リンクはまだない。

image

Auth サービス

servicesフォルダを作ってそこにAuthenticationのロジックを書き込む。

// src/services/auth.js

export const isBrowser = () => typeof window !== "undefined"

export const getUser = () =>
  isBrowser() && window.localStorage.getItem("gatsbyUser")
    ? JSON.parse(window.localStorage.getItem("gatsbyUser"))
    : {}

const setUser = user =>
  window.localStorage.setItem("gatsbyUser", JSON.stringify(user))

export const handleLogin = ({ username, password }) => {
  if (username === `jabba` && password === `password`) {
    return setUser({
      username: `jabba`,
      name: `Jabba`,
      email: `[email protected]`,
    })
  }

  return false
}

export const isLoggedIn = () => {
  const user = getUser()

  return !!user.username
}

export const logout = callback => {
  setUser({})
  callback()
}

getUser ユーザー情報の読み取り

setUser ユーザー情報の保存

handleLogin ログイン処理。今回はサーバー側が無いのでログイン名jabbaパスワードpasswordをそのまま書いた。

isLoggedIn 現在のログイン状態の読み取り

logout ログアウト処理。

ログインページの作成

まずはloginのコンポーネントから作る。

// src/components/login.js

import React from "react"
import { navigate } from "gatsby"
import { handleLogin, isLoggedIn } from "../services/auth"

class Login extends React.Component {
  state = {
    username: ``,
    password: ``,
  }

  handleUpdate = event => {
    this.setState({
      [event.target.name]: event.target.value,
    })
  }

  handleSubmit = event => {
    event.preventDefault()
    handleLogin(this.state)
  }

  render() {
    return (
      <>
        <h1>Log in</h1>
        <form
          method="post"
          onSubmit={event => {
            this.handleSubmit(event)
            navigate(`/`)
          }}
        >
          <label>
            Username
            <input type="text" name="username" onChange={this.handleUpdate} />
          </label>
          <label>
            Password
            <input
              type="password"
              name="password"
              onChange={this.handleUpdate}
            />
          </label>
          <input type="submit" value="Log In" />
        </form>
      </>
    )
  }
}

export default Login

ログイン用のフォームとしてユーザー名とパスワードを入れるところを表示する。

書き込まれたらonChangeで発火させてstateを更新する。SubmitされたらhandleLoginにわたす。

これに合わせてpageを作る。

// src/pages/login.js

import React from "react"
import Layout from "../components/layout"
import Login from "../components/login"

const LoginPage = ({ location }) => (
  <Layout location={location}>
    <Login />
  </Layout>
)

export default LoginPage

pageに入れるとそのままパスが作られる。

この時点で http://localhost:8000/login にアクセスが可能になっていて、こんな画面が出る。

image

ユーザー名:jabba パスワード:password

を入れるとログインできる。けど、特になにも起こらない。

プロファイルページを作る

まずprofileコンポーネントから。

// src/components/profile.js

import React from "react"
import { navigate } from "gatsby"
import { getUser, isLoggedIn } from "../services/auth"

class Profile extends React.Component {
  componentDidMount() {
    if (!isLoggedIn()) {
      navigate(`/login`)
    }
  }

  render() {
    return (
      <>
        <h1>Your profile</h1>
        <ul>
          <li>Name: {getUser().name}</li>
          <li>E-mail: {getUser().email}</li>
        </ul>
      </>
    )
  }
}

export default Profile

やってることは、もしログインしてなかったらloginページへ飛ぶ。ログインしていたら、名前とemailを表示する。

// src/pages/profile.js

import React from "react"
import Layout from "../components/layout"
import Profile from "../components/profile"

const ProfilePage = ({ location }) => (
  <Layout location={location}>
    <Profile />
  </Layout>
)

export default ProfilePage

nav-barを書き換える。

// src/components/nav-bar.js

import React from "react"
import { Link } from "gatsby"
import { getUser, isLoggedIn, logout } from "../services/auth"

class NavBar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      greetingMessage: ""
    }
  }

  componentDidMount() {
    let greetingMessage = ""
    if (isLoggedIn()) {
      greetingMessage = `ハロー ${getUser().name} さん`
    } else {
      greetingMessage = "You are not logged in"
    }
    this.setState({
      greetingMessage: greetingMessage
    })
  }

  render() {
    return(
      <div
        style={{
          display: "flex",
          flex: "1",
          justifyContent: "space-between",
          borderBottom: "1px solid #d1c1e0",
        }}
      >
        <span>{this.state.greetingMessage}</span>

        <nav>
          <Link to="/">Home</Link>
          {` `}
          {isLoggedIn()
            ? (
              <>
                <Link to="/profile">Profile</Link>
                {` `}
              </>
            )
            : (
              <Link to=/login”>Login</Link>
            )
          }
        </nav>
      </div>
    )
  }
}

export default NavBar

nav-barにやったこと。もしログイン中だったら「ハロー<ユーザー名>」の挨拶を入れる。ログイン中でなかったら”You are not logged in”のメッセージを入れる。メッセージ設定をcomponentDidMountの中に入れてるのはSSRのエラーを防ぐため。

ここまでで、ログインした状態でトップページを見るとこのように挨拶が出ている。

image

nav-bar のプロファイルリンクも有効になっていて、http://localhost:8000/profile に飛ぶとこのように情報が出ている。

image

logout 処理

nav-barにログアウト処理を加える。

// src/components/nav-bar.js

import React from "react"
import { Link } from "gatsby"
import { getUser, isLoggedIn, logout } from "../services/auth"

class NavBar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      greetingMessage: ""
    }
  }

  componentDidMount() {
    let greetingMessage = ""
    if (isLoggedIn()) {
      greetingMessage = `ハロー ${getUser().name} さん`
    } else {
      greetingMessage = "You are not logged in"
    }
    this.setState({
      greetingMessage: greetingMessage
    })
  }

  logout = (event) => {
    event.preventDefault();
    this.setState({
      greetingMessage: "You are not logged in"
    })
  }

  render() {
    return(
      <div
        style={{
          display: "flex",
          flex: "1",
          justifyContent: "space-between",
          borderBottom: "1px solid #d1c1e0",
        }}
      >
        <span>{this.state.greetingMessage}</span>

        <nav>
          <Link to="/">Home</Link>
          {` `}
          {isLoggedIn()
            ? (
              <>
                <Link to="/profile">Profile</Link>
                {` `}
                <Link to="/" onClick={logout} >
                  Logout
                </Link>
              </>
            )
            : (
              <Link to="/login">Login</Link>
            )
          }
        </nav>
      </div>
    )
  }
}

export default NavBar

やってること。もしログイン状態だったらログアウトリンクを表示して、クリックされたらログアウト処理してトップページへ飛ぶ。Reactではそのコンポーネントのstateが更新されると再評画が走る。logoutメソッドの中でgreetingMessageを更新しているので、再評画されて新しいメッセージが出る。

ここまででLogoutリンクが出て押すとログアウトされて”You are not logged in“の表示が出る。

image

コンテンツのリンクを整える

Gatsbyのcontentフォルダにマークダウンで入れたファイルは自動的にページとして生成される。その際のリンクもフォルダ名が入る。例えば今の状態だとひとつめのブログ記事「New Beginnings」はhttp://localhost:8000/new-beginnings/に入っている。このままではいつかloginとかprofileなんて記事をもし書いたら衝突が起きてしまう。

ブログの場合はそこを絶対に衝突させないようによく日付を数列にして入れている。

例えばhello-worldというフォルダを20200401-hello-worldという風に。これだと同じ日に同じタイトルを入れるようなことをしない限りは衝突が起きない。

後はこちらの手順にしたがって、NetlifyなりGitLab pagesにデプロイしてできあがり。

ソースコードはこちらに置いたので細かいところはこちらを参照してください。少なくともこのコードは動いてる。

https://gitlab.com/Jabba14/gatsby_login

最終的にできあがったウェブサイトがこんな感じになった。

https://jabba14.gitlab.io/gatsby_login/

image

まとめ

静的サイトジェネレーターといっても基本はReactベースなのでなんでもできる。Gatsbyの爆速機能にのっかって、そこに自分が欲しい分だけを追加するのもいい方法。

今回はサーバー側については一切書いていない。なぜならGatsby+Firebaseとかの連携に興味ある人が居るのか、ちょっと疑問だったから。まずは「Gatsbyは静的とは言ってもフルカスタマイズ可能だし、いろいろできますよ」とだけ言いたかったのでこの記事を書いた。

関連記事


海外移住のQ&Aサイト シティーズ


海外移住向けのQ&Aサイトを運営しています。現在、海外にお住まいの方、これから海外移住や留学、転職をお考えの方はぜひご参加ください。 登録はもちろん無料です。

シティーズ https://www.cityz.jp/


cityz



  • © copyright jabba.cloud 2020