- Published on
Gatsbyにログイン機能をつけてみよう
- Authors
- ジャバ・ザ・ハットリ
Gatsby は実はカンタンにログイン機能をつけることができる。ユーザーがシステムにログインして固有の情報を見せる機能。例えばジャスティン氏がログインしたら「ジャスティン・ビーバーさん、今日もあたしの Gatsby にログインしてくれてありがとう(ハート)」みたいなメッセージを出すとか。
Gatsby の爆速に惚れて「なんか Gatsby で作ろうかな」となったとする。用途はブログでも創作物の作品集でもランディングページでもいい。「ログイン機能を付けたいんだけどなー。でもこれは『静的サイト』だろ。無理かな?」と考えてしまっていたら「それカンタンにできますよ」と言いたい。フルカスタマイズが可能な Gatsby の場合、だいたいなんでもできる。
もろろん会員情報を入れておくためのサーバーとして Firebase や Rails が必要にはなる。表側の爆速処理は全部 Gatsby にやってもらって、裏側の会員情報やらをサーバーにやってもらう、となる。
ブログのようにほとんどの情報出力が静的な内容で、たまーにユーザーログインが要る、ってぐらいならこれで十分。
Gatsby には豊富はプラグインがあるので、だいたいの機能追加にはプラグインをインストールすれば1発で終わる。でもこのログイン機能にはプラグインが無い。それはサーバー側の構成によって変わるし、プラグインの作りようが無いからだと思う。
ただそんなに難しくもないのでこの解説を読んで試してみてはいかがでしょうか?
ここではサーバー側は無しであくまで Gatsby の設定だけを書いた。
この解説で完成するログイン機能ありのブログはこちら。https://jabba14.gitlab.io/gatsby_login/
ソースコードはこちら。https://gitlab.com/Jabba14/gatsby_login
ネット上の技術解説記事でレポジトリーが公開されてないと「この記事の通りにやったら本当にこれは動くんかいな」と不安になりますよね。そういう不安ある方はこちらからお試しください。
公式サイトの解説はこちら(このブログ記事では内容をちょっと変えた)
スターターキットから開始する
こちらの Gatsby をはじめる手順にしたがって、まずはスターターキットから選ぶ。
今回は公式スターターキットであるgatsby-starter-blogにした。やっぱり公式の方が手堅い。
このコマンドで入れる。
$ gatsby new gatsby_login https://github.com/gatsbyjs/gatsby-starter-blog
Gatsby ブログラムが走ってインストールされる。
完了したら、フォルダにいって立ち上げる。
$ cd gatsby_login
$ gatsby develop
ブラウザで http://localhost:8000/
にアクセスするとこんなブログができている。
確認できたらここまでを 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>
</>
)
}
// 以下変更無し
この時点で以下のようなナビゲーションバーがヘッダーに表示されているはず。リンクはまだない。
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
にアクセスが可能になっていて、こんな画面が出る。
ユーザー名: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 のエラーを防ぐため。
ここまでで、ログインした状態でトップページを見るとこのように挨拶が出ている。
nav-bar のプロファイルリンクも有効になっていて、http://localhost:8000/profile
に飛ぶとこのように情報が出ている。
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“の表示が出る。
コンテンツのリンクを整える
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/
まとめ
静的サイトジェネレーターといっても基本は React ベースなのでなんでもできる。Gatsby の爆速機能にのっかって、そこに自分が欲しい分だけを追加するのもいい方法。
今回はサーバー側については一切書いていない。なぜなら Gatsby + Firebase とかの連携に興味ある人が居るのか、ちょっと疑問だったから。まずは「Gatsby は静的とは言ってもフルカスタマイズ可能だし、いろいろできますよ」とだけ言いたかったのでこの記事を書いた。