# 2.Next.jsを使ってJamstackなアプリを作る
# 概要
- この章ではビルド時にAPIからデータを取得してページを生成する、というJamstackの特徴を持ったアプリを作成していきます
# ゴール
- Next.jsを使ってWebアプリを作成できていること
- Next.jsの機能を使ってビルド時にAPIからデータを取得できていること
- 作成したWebアプリをnowにデプロイし公開できていること
# 2-1.Next.jsの雛形を作成
- Next.jsを使ってアプリを作成していきます
- Next.jsの雛形生成ライブラリである
create-next-app
と、コマンドラインツールであるyarn
をインストールします
npm i -g create-next-app yarn
create-next-app
を使ってアプリを作成します- 途中で選択肢が出たら
Default starter app
を選択します
- 途中で選択肢が出たら
create-next-app jamstack-sample
- jamstack-sampleというフォルダが作成されてその中にアプリの雛形が生成されました
- ログに表示される案内に従って起動できることを確認しましょう
cd jamstack-sample
yarn dev
2
TIP
止めたいときは Ctl + c
で停止できます
- http://localhost:3000にアクセスすると以下の画面が表示されるはずです
TIP
今後出てくるコマンドの実行は特別な案内がない限りjamstack-sample
ディレクトリ内(=yarn dev
を実行した場所)で実行してください
- 動きが確認できたらVSCodeでjamstack-sampleプロジェクトを開いておきましょう
# 2-2.記事一覧ページの作成
- Qiitaの新着記事一覧を表示するページを作成していきます
# 埋め込みデータで記事一覧ページの作成
- まずは新しいページを作成してHelloだけ表示させてみましょう
pages/items/index.js
というファイルを作成して以下の内容を記述してください
TIP
pages
配下のディレクトリ構成がそのままURL構造に適用されます。pages/items/index.js
はhttp://localhost:3000/items/index
にマッピングされます。一般的に/index
は省略するのでhttp://localhost:3000/items
にマッピングされることになります。
function Items() {
return (
<div>
<h1>Hello</h1>
</div>
);
}
export default Items;
2
3
4
5
6
7
8
9
- http://localhost:3000/itemsにアクセスしてHelloが表示されることを確認しましょう
- 表示が確認できたらダミーの記事一覧を表示するように修正してみます
// ダミーの記事一覧を格納した配列を定義
const items = [
{ id: 1, title: '記事のタイトル1' },
{ id: 2, title: '記事のタイトル2' },
{ id: 3, title: '記事のタイトル3' },
];
function Items() {
return (
<div>
<h1>Hello</h1>
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
export default Items;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TIP
.map
を使うと配列の要素を順番にループ処理できます。上のコードでは記事の数だけliタグを生成しています
# QiitaのAPIから取得したデータを表示する
- QiitaのAPIをたたくので、通信処理を実行するためのライブラリをインストールします
yarn add node-fetch
pages/items/index.js
を修正してQiitaのAPIからデータを取得するようにします
// 通信ライブラリであるnode-fetchをimport
import fetch from 'node-fetch';
// getStaticPropsから渡されるitemsという変数を受け取る
function Items({ items }) {
return (
<div>
<h1>Hello</h1>
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
// getStaticPropsという名前の関数はビルド時にフレームワークが実行してくれる
export async function getStaticProps() {
// QiitaのAPIをコール
const res = await fetch('https://qiita.com/api/v2/items', {
// アクセストークンをセット
headers: {
Authorization: 'Bearer a8f7b4026700cd36eb8e3a75525d767d0115aabe',
},
});
const data = await res.json();
// APIから取得したデータを必要な項目(idとtitle)だけに絞り込む
const items = data.map(item => ({ id: item.id, title: item.title }));
// 取得したデータをpropsとしてreturnするとItems関数の引数に渡すことができる
return { props: { items } };
}
export default Items;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
TIP
QiitaのAPIはアクセス回数に制限があるためトークンを発行してセットしておいてください。Qiitaのアカウントがなかったりトークンの発行手順がわからない場合は上記サンプルに埋め込まれているトークンを使ってください。マイページの「アプリケーション」からトークンを発行できます。
TIP
Next.jsの機能としてgetStaticPropsという名前で関数を定義するとビルド時に処理が実行され結果をコンポーネントに渡すことができます。ここでAPIをたたく処理を行うことでビルド時にデータを取得しコンポーネントに渡しています。(コンポーネントとはHTMLをreturnしているfunctionのこと)
- 修正後http://localhost:3000/itemsにアクセスするとQiitaの最新記事が表示されているはずです
- Qiitaの最新記事一覧はこちらのページで確認できます
# 2-3.記事詳細ページの作成
- 一覧画面で記事を選択すると詳細ページが表示されるようにしてみましょう
- 詳細ページのURLは
/items/記事のID
となるようにします- ex.
http://localhost:3000/items/4075d03278d1fb51cc37
- ex.
# 記事一覧に記事詳細へのリンクを追加する
- 記事詳細ページはこの後作りますが、先に一覧から詳細へ遷移できるように修正しておきます
pages/items/index.js
にLinkを追加します
// ページ遷移をするためのLinkコンポーネントをimport
import Link from 'next/link';
import fetch from 'node-fetch';
function Items({ items }) {
return (
<div>
<h1>Hello</h1>
<ul>
{items.map(item => (
<li key={item.id}>
{/* Linkを追加 */}
<Link href="/items/[id]" as={`/items/${item.id}`}>
<a>{item.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}
export async function getStaticProps() {
const res = await fetch('https://qiita.com/api/v2/items', {
// アクセストークンをセット
headers: {
Authorization: 'Bearer a8f7b4026700cd36eb8e3a75525d767d0115aabe',
},
});
const data = await res.json();
const items = data.map(item => ({ id: item.id, title: item.title }));
return { props: { items } };
}
export default Items;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- 遷移先はエラーになりますが一覧がリンク化されました
# 詳細ページを作成する
- 一覧画面は
/items
にマッピングさせたいのでpages/items/index.js
というファイルを作りましたが、今回作りたいページは/items/記事のID
なのでURLが動的に変動します - そういう場合は
[id].js
といったファイル名で作成することで対応できます pages/items/[id].js
を作成し以下の内容を記述してください
import fetch from 'node-fetch';
// getStaticPropsからitemを受け取る
function Item({ item }) {
return (
<div>
<h1>{item.title}</h1>
<hr />
<div dangerouslySetInnerHTML={{ __html: item.body }}></div>
</div>
);
}
// ビルド時に実行される関数で、returnした値をコンポーネントに渡すことができる
export async function getStaticProps({ params }) {
// QiitaのAPIから記事の詳細情報を取得
const res = await fetch(`https://qiita.com/api/v2/items/${params.id}`, {
// アクセストークンをセット
headers: {
Authorization: 'Bearer a8f7b4026700cd36eb8e3a75525d767d0115aabe',
},
});
const data = await res.json();
// レスポンスから必要な項目だけを抽出
const item = { id: data.id, title: data.title, body: data.rendered_body };
// 抽出した値をreturn(コンポーネントに引数として渡される)
return { props: { item } };
}
// ビルド時に実行される関数で、[id].jsのidに具体的にどんな値が入るのかをリストでreturnする
export async function getStaticPaths() {
// QiitaのAPIから記事一覧の情報を取得
const res = await fetch('https://qiita.com/api/v2/items', {
// アクセストークンをセット
headers: {
Authorization: 'Bearer a8f7b4026700cd36eb8e3a75525d767d0115aabe',
},
});
const data = await res.json();
// レスポンスを元に詳細ページのURLのリストを作成
const paths = data.map(item => `/items/${item.id}`);
return { paths, fallback: false };
}
export default Item;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
TIP
Jamstackはビルド時に各ページのHTMLを生成するため、今回の記事詳細ページのような場合でも全てのページのHTMLを作成しておく必要があります。どのようなURLのページがあるかはAPIを叩いてみないとわからないので動的に指定する必要がありますが、getStaticPathsを使うとそれを実現することができます。
- ここまでできたら一覧画面から詳細画面へ遷移してみましょう
- 以下のように記事の本文が表示されているはずです
# ビルドしてみる
- これまでは
yarn dev
コマンドで開発モードで起動していました - 本番用にビルドして成果物を確認してみます
yarn build
- このようなログがでます
- build後の成果物は
.next
ディレクトリに作成されます .next
の内部はいろいろなファイルがあって複雑ですが、一覧画面と各詳細画面のhtmlファイルが生成されていることを確認できます
- 以下のコマンドでビルドしたアプリを起動することができます
yarn start -p 3001
TIP
デフォルトではyarn dev
と同じ3000番ポートで起動するのでオプションをつけて3001番で起動しています。http://localhost:3001/itemsにアクセスしてみてください。
yarn dev
のときは都度ビルドが走るため最新記事が都度反映されていましたyarn start
の場合はビルド済みのアプリを起動するだけなので、再ビルドするまで記事一覧の内容は更新されません- APIをたたくのがビルド時のみで、実行時はAPIをたたかないのがJamstackの特徴でしたね
# リファクタリング
- 通信処理が何度も出てくるので専用のファイルに切り出しておきましょう
api/qiitaApi.js
を作成して以下の内容を記述してください- pagesディレクトリと横並びになる位置にapiディレクトリを作成してください
import fetch from 'node-fetch';
const baseUrl = 'https://qiita.com/api/v2';
const headers = {
Authorization: 'Bearer a8f7b4026700cd36eb8e3a75525d767d0115aabe',
};
// 記事一覧を取得する関数
export async function getItems() {
const res = await fetch(`${baseUrl}/items`, { headers });
return res.json();
}
// 記事詳細を取得する関数
export async function getItem({ id }) {
const res = await fetch(`${baseUrl}/items/${id}`, { headers });
return res.json();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 作成したqiitaApiの内容を適用していきます
pages/items/index.js
を修正します
import Link from 'next/link';
// qiitaApiから一覧を取得するgetItemsをimport
import { getItems } from '../../api/qiitaApi';
function Items({ items }) {
return (
<div>
<h1>Hello</h1>
<ul>
{items.map(item => (
<li key={item.id}>
<Link href="/items/[id]" as={`/items/${item.id}`}>
<a>{item.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}
export async function getStaticProps() {
const data = await getItems();
const items = data.map(item => ({ id: item.id, title: item.title }));
return { props: { items } };
}
export default Items;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pages/items/[id].js
を修正します
// qiitaApiから一覧を取得するgetItemsと詳細を取得するgetItemをimport
import { getItems, getItem } from '../../api/qiitaApi';
function Item({ item }) {
return (
<div>
<h1>{item.title}</h1>
<hr />
<div dangerouslySetInnerHTML={{ __html: item.body }}></div>
</div>
);
}
export async function getStaticProps({ params }) {
const data = await getItem({ id: params.id });
const item = { id: data.id, title: data.title, body: data.rendered_body };
return { props: { item } };
}
export async function getStaticPaths() {
const data = await getItems();
const paths = data.map(item => `/items/${item.id}`);
return { paths, fallback: false };
}
export default Item;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
yarn dev
で起動して変わらずに動くことを確認しておいてください
# 2-4.アプリをnowにデプロイする
- これまでは端末のローカル環境で起動していたのでその端末からしかアクセスできませんでした
- ホスティングサービスにアプリをアップロードしてどこからでもアクセスできるように公開してみましょう
- 今回はnowというホスティングサービスを使います
# アカウントの作成
- まずはGitHubのアカウントを作成します
- 以下のページからアカウントを作成してください
- 次にnowのアカウントを作成します
- GitHubアカウントを使ってnowのアカウントを作成できます
# コマンドラインツールのセットアップ
- nowのコマンドラインツールをインストールします
npm i -g now@latest
- インストールできたらコマンドラインでnowにログインします
- メールアドレスの入力を求められるのでGitHub登録時のメールアドレスを入力してください
- メールが飛ぶのでVerifyを押すとログインできます
now login
- 以下のようなメールが届くのでVerifyを押します
# デプロイする
- 以下のコマンドでデプロイします
- いろいろ聞かれるので全てデフォルトのままエンターでOK
- デプロイが実行されるので少し時間がかかります
now
- デプロイが完了するとURLが表示されます
- アクセスするとローカルで動かしていたのと同じアプリが表示されます!
# 2-5.レイアウトを整える
- Jamstackとは直接関係ありませんがせっかくなのでレイアウトを整えておきます
- 今回はReactBootstrapというライブラリを使います
# ReactBootstrapのセットアップ
- 必要なライブラリをインストールします
yarn add react-bootstrap bootstrap
- 設定ファイルを作成します
pages/_app.js
を作成して以下の内容を記述してください
import 'bootstrap/dist/css/bootstrap.css'
import App from 'next/app'
export default App
2
3
4
# ReactBootstrapのコンポーネントを適用する
import Link from 'next/link';
// react-bootstrapからコンポーネントをimport
import { Container, ListGroup } from 'react-bootstrap';
import { getItems } from '../../api/qiitaApi';
function Items({ items }) {
return (
<Container>
<h1>Hello</h1>
<ListGroup>
{items.map(item => (
<Link key={item.id} href="/items/[id]" as={`/items/${item.id}`}>
<ListGroup.Item action>{item.title}</ListGroup.Item>
</Link>
))}
</ListGroup>
</Container>
);
}
export async function getStaticProps() {
const data = await getItems();
const items = data.map(item => ({ id: item.id, title: item.title }));
return { props: { items } };
}
export default Items;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 見た目の雰囲気が変わりました
- 記事詳細画面も修正します
pages/items/[id].js
を修正してください- ここではContainerを使います
// react-bootstrapからコンポーネントをimport
import { Container } from 'react-bootstrap';
import { getItem, getItems } from '../../api/qiitaApi';
function Item({ item }) {
return (
<Container>
<h1>{item.title}</h1>
<hr />
<div dangerouslySetInnerHTML={{ __html: item.body }}></div>
</Container>
);
}
export async function getStaticProps({ params }) {
const data = await getItem({ id: params.id });
const item = { id: data.id, title: data.title, body: data.rendered_body };
return { props: { item } };
}
export async function getStaticPaths() {
const data = await getItems();
const paths = data.map(item => `/items/${item.id}`);
return { paths, fallback: false };
}
export default Item;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 詳細画面も見た目の雰囲気が変わりました
- 修正版をデプロイします
- 二度目以降のデプロイはデフォルトだとdev環境へのデプロイになるので
--prod
をつけます
- 二度目以降のデプロイはデフォルトだとdev環境へのデプロイになるので
now --prod
- デプロイが完了すると修正版が公開されているはずです
# まとめ
create-next-app
によるNext.jsを使ったアプリの作成のしかたを学びましたgetStaticProps
などNext.jsの機能を使うことでビルド時にAPIからデータを取得しJamstackなアプリにすることができました- nowにデプロイすることで作成したアプリを世の中に公開することができました