【Next.js】getServerSidePropsでJSON一覧から個別情報を取得するメモ
概要
Next.jsでJSONファイルなど情報を取ってくることが多いと思うが、Next.jsの場合、getServerSidePropsを使って取ってくる事ができるので防備ログとして残す。
なお、getStaticPropsとりあえず今回はなしで。
最終目的
最終的な目的として、以下のことを最終目的とする。
- jsonファイルからデータを取得して一覧表示する
- 一覧をクリックしたら個別ページにその情報を表示する
getServerSidePropsとは?
Next.js では、[param] のようにして角括弧([])を使って動的なルーティングを作成できる。
これをダイナミックルーティングというがgetServerSideProps/getStaticPropsを機能使用して実現できる。
getServerSidePropsはNext.jsで使うことのできる関数で、関サーバーサイドで実行される。
ちなみgetStaticPropsとの違いは
- getStaticPropsはビルドのタイミングで実行される
- getServerSidePropsはリクエスト毎に実行される
その為、リクエスト毎にレスポンスを返すのでgetStaticPropsと違って、読み込みに時間がかかることがある。またNext.jsでプロジェクトを作成すると「pages」ディレクトリが作成されるのですが、getServerSidePropsもgetStaticPropsも「pages」ディレクトリ以外は使えない。
ダイナミックルーティングとは?
Next.js では、pages/posts/[id].jsのようなファイル名でページを作成すると、1つのファイルで、以下のようなURLでアクセスができる。
/posts/01
/posts/02
/posts/03
これを ダイナミックルーティング機能というらしい。
実際の流れ
では、実際の流れをメモしていく。今回はJSONファイルとしてjsonplaceholderというダミーのjsonファイルを用意してくれているサービスを利用して、今回は記事を想定して以下のURLから記事データを取得する。
一覧を読み込むページはpage/index.jsで表示します。
JSONを読み込んで一覧ページを作成
実際に読み込み処理を書きます。以下のjsonファイルを読み込みます。
https://jsonplaceholder.typicode.com/posts
//1つの記事のスキーマは以下のようになってる
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi a"
},
JSONファイルを読み込む
jsonファイルを読み込む処理は以下。
export default function Home() {
return (
<div>
<h1>一覧を表示させる</h1>
</div>
)
}
export async function getServerSideProps() {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts`)
const posts = await res.json()
return { props: { posts } }
}
getServerSideProps はHome()の外でexportを利用して使用する。
getServerSideProps 内でfetch()で取得したデータからJSONを取得しDOM操作に利用する必要があるので、getServerSidePropsにasyncを付けて、fatch()とres.json()にはawaitをつけて非同期処理としています。
その結果を返してあげる必要があるので、return{}でpropsプロパティにオブジェクトでデータを渡す。
また上記のコードはNext.jsの公式サイトに載っていて、割と外部APIからデータを取ってくるときのベースとなる書き方のようである。
JSONファイルを表示する
今度は取得したJSONファイルを表示する必要がある。
export default function Home(data) {
return (
<div>
<h1>一覧を表示させる</h1>
<ul>
{data.posts.map((item)=>{
return(
<li key={item.id}>
<Link href={`/posts/${item.id}`}>
<a>{item.title}</a>
</Link>
</li>
)
})}
</div>
)
}
JSONファイルはgetServerSideProps のpropsに渡したデータは、Home(data)に引数から受け取ることができる。プロパティ名をdataとしているがなんでもいい。
その後、map関数で各データを取り出して表示してます。
ちなみに、data.postsの「posts」という名前は、getServerSideProps のprops: { posts }で渡している変数名。
またルーティング用のコンポーネントであるLINKには、JSONファイルには各記事の「id」があったのでhrefに設定しておきます。hrefのパスの階層は後に作成する個別ファイル([id].js)と同じ階層にする。
この「id」番号は記事詳細のファイルで再度fetch()で読み込む際に必要となります。
個別ページを作成・表示
一覧ページを作成したら今度は個別ページを作成します。
個別ページでも同様に、getServerSideProps を利用します。
個別ファイルを作る
今回はpages/posts/[id].jsとしたいので、pagesディレクトリにpostsフォルダを作成してそこに[id].jsを作成。
[id].jsにも同様に、getServerSidePropsの処理を記載しますが、ほとんど前回と同じ。
違いは、getServerSidePropsの引数にcontextが設定されてる事。
export const getServerSideProps=async (context)=>{
console.log(context)
const id=context.query.id
const res=await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
const post=await res.json()
return{
props:{post}
}
}
console.log(context)で確認するとわかるが、このcontextにいろんな情報が格納されている。
今度は記事一覧ではなく、各記事のデータが欲しいので、JSONファイルから各記事の「id」を指定したURLからfetch()による非同期処理で取得する必要がある。
各記事の「id」番号がcontextのquery(クエリ文字列を表すオブジェクト)の中にid番号が入ってる。
query: { id: '6' },
この「id」は一覧ページでhref属性に設定した記事の「id」番号である。
ちなみに今回[id].jsとファイル名を指定しているが、[post].jsだった場合は、contextのqueryは以下になる。
query: { post: '6' },
あとは同様にpropsに記事のデータを指定してreturnしてあげる。
contextからidを取得する際の注意点
contextからidを取得する場合は、query(クエリパラメータ)から取得していましたが、
contextの中身を見るとparamsの中にもidがあることが分かります。
query: { id: '4' },
resolvedUrl: '/posts/4',
params: { id: '4' },
その為、以下のようにどちらからでも取得できる。
export const getServerSideProps=async (context)=>{
const id=context.query.id
}
getStaticPropsの場合は、query'(クエリパラメータ)は存在せず、params`になるので以下のようになる。
export const getStaticProps=async (context)=>{
const id=context.params.id
}
記事の内容を表示する
こちらは一覧ページと同じである。
export default function post(param) {
return (
<>
<h1>POST(投稿IDは){param.post.id}</h1>
<h2>タイトル:{param.post.title}</h2>
<div>
<p>本文</p>
{param.post.body}
</div>
</>
)
}
paramという引数を指定してあげて、その中に記事のタイトル(param.post.title)や本文(param.post.body)の内容が格納されてるので、出力すれば表示される。
一覧同様でparam.postのpostは、getServerSidePropsで指定したprops:{post}と同じ変数名でアクセスできる。
以上で記事の内容が表示される。
分割代入による引数の設定
ちょっとした余談。
一覧ページや記事の個別ページでも引数を受け取る際に、分割代入を利用すれば省略して記載できる。
//通常のパターン
export default function post(param) {
return (
<>
<h2>タイトル:{param.post.title}</h2>
</>
)
}
//分割代入で引数を指定
export default function post({post}) {
return (
<>
<h2>タイトル:{post.title}</h2>
</>
)
}
参考サイト
Discussion