Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Next.js のデータフェッチについて

aku11i
November 24, 2021

Next.js のデータフェッチについて

2021/11/24 に開催された【Next.js Update!】v12リリースを踏まえ、Next.jsの採用を考える( https://forkwell.connpass.com/event/228457/ )での事例講演で使用したスライドです。
YouTube: https://www.youtube.com/watch?v=KaS3bgz_CA4

前セッション:Web Frontend Unit の立ち上げとリアーキテクチャに際してNext.js を採用したワケ
https://www.slideshare.net/IidaYukako/nextjs-update-20211124-web-frontend-unit-nextjs

aku11i

November 24, 2021
Tweet

Other Decks in Programming

Transcript

  1. 2 自己紹介 芥川 周平 2017.04 - 受託開発の会社でWeb系の新規開発を4つほど経験 2021.09 - 株式会社アルファドライブ

    / 株式会社ニューズピックス に所属 Web Frontend Unit で NewsPicks のリアーキテクチャを行っている 兵庫県姫路市🏯からフルリモート @aku11i
  2. 6 NewsPicks のリアーキテクチャについて NewsPicks ではフロントエンドのリアーキテクチャを行っている • モノリスな Web 基盤からフロントエンド部分を剥がし、 Next.js

    に載せ替えている • 初期段階ではページの見た目や機能はそのままに基盤だけを載せ換える方針 • 新基盤でも画面挙動を合わせたいので レンダリング前に必要なデータを全て取得しておく必要がある → getInitialProps or getServerSideProps を活用する
  3. 8 現行基盤 Webサーバー DB リクエストの度に HTML を構築して返却 新基盤 BFF getInitialProps

    or getServerSideProps NewsPicks のリアーキテクチャについて getInitialProps を採用
  4. 9 • 初めは getServerSideProps を使用していた • どのページでも必要になる共通データの BFF へのリクエストを抑えたい話が出た ◦

    ヘッダーに表示するユーザー情報など • Apollo Client で GraphQL のクエリ結果をキャッシュするのがシンプルっぽい 👀 ◦ getServerSideProps では実現できなかった(詳細は次ページで解説) → getInitialProps を採用することに なぜ getInitialProps ?
  5. 12 キャッシュを利用できる → 一度実行したクエリは キャッシュからデータを取り出せる BFF BFF getInitialProps (初回) getServerSideProps

    getInitialProps (2回目以降) 様々なユーザーのクエリを実行する のでキャッシュを利用できない データフェッチと Apollo Client のキャッシュ について
  6. 13 • 私の認識: ◦ getInitialProps はサーバーとクライアントの両方で実行され、 ▪ Node.js と Browser

    に依存しないコードが求められる ▪ クライアント向けのバンドルにデータフェッチ部分のコードが混入する ◦ といった管理の難しさがある ◦ 上記を解決するためにより良い選択肢として getServerSideProps / getStaticProps が登場 • 現行基盤の画面挙動と合わせつつキャッシュを使うために getInitialProps を選択 • 新規に設計をする場合はまず getServerSideProps / getStaticProps の使用を検討 getInitialProps の立ち位置 非推奨ではない getInitialProps を推奨するわけではないです。 非推奨ではないのでまだ使えるという話です!
  7. 16 getInitialProps (getServerSideProps) 、膨らみがち🔥 同じような処理が複数ページに散らばりがち  → 複数ファイルに変更が及びがち 再利用できる形にしたい ♻ 目的:メンテしやすく、再利用しやすく

    高階関数(Higher-order function)にしてみる データフェッチ処理を再利用したい ” 少なくとも以下のうち1つを満たす関数である。 • 関数(手続き)を引数に取る • 関数を返す 高階関数 - Wikipedia 高階関数とは
  8. 17 const MyPage: NextPage<MyPageProps> = ({ name }) => {

    return <h1>Hello {name} !</h1> } MyPage.getInitialProps = async (context) => { const user = await getUser() if (!user) { return redirect('/login', context.res) } return { name: user.name } } export default MyPage 例:ユーザー認証が必要なマイページ 認証されていない場合は /login へ リダイレクトする データフェッチ処理を再利用したい getServerSideProps / getStaticProps でもできます
  9. 18 const MyPage: NextPage<MyPageProps> = ({ name }) => {

    return <h1>Hello {name} !</h1> } MyPage.getInitialProps = async (context) => { const user = await getUser() if (!user) { return redirect('/login', context.res) } return { name: user.name } } export default MyPage 例:ユーザー認証が必要なマイページ 認証されていない場合は /login へ リダイレクトする ここを切り出したい データフェッチ処理を再利用したい
  10. 19 export function withAuthenticated<Props>( getInitialProps?: GetInitialPropsWithUser<Props>, ): GetInitialProps<Props> { return

    (context) => { const user = getUser() if (!user) { return redirect('/login', context.res) } if (getInitialProps) return getInitialProps(context, user) } } ユーザー認証を行う高階関数を作ってみる type GetInitialProps<Props> = ( context: NextPageContext, ) => Props | Promise<Props> type GetInitialPropsWithUser<Props> = ( context: NextPageContext, user: User, ) => Props | Promise<Props> 型は自作
  11. 20 export function withAuthenticated<Props>( getInitialProps?: GetInitialPropsWithUser<Props>, ): GetInitialProps<Props> { return

    (context) => { const user = getUser() if (!user) { return redirect('/login', context.res) } if (getInitialProps) return getInitialProps(context, user) } } ユーザー認証を行う高階関数を作ってみる type GetInitialProps<Props> = ( context: NextPageContext, ) => Props | Promise<Props> type GetInitialPropsWithUser<Props> = ( context: NextPageContext, user: User, ) => Props | Promise<Props> 型は自作 getInitialProps を受け取り getInitialProps を返す (高階関数)
  12. 21 export function withAuthenticated<Props>( getInitialProps?: GetInitialPropsWithUser<Props>, ): GetInitialProps<Props> { return

    (context) => { const user = getUser() if (!user) { return redirect('/login', context.res) } if (getInitialProps) return getInitialProps(context, user) } } type GetInitialProps<Props> = ( context: NextPageContext, ) => Props | Promise<Props> type GetInitialPropsWithUser<Props> = ( context: NextPageContext, user: User, ) => Props | Promise<Props> 型は自作 ユーザー認証を行う高階関数を作ってみる
  13. 22 export function withAuthenticated<Props>( getInitialProps?: GetInitialPropsWithUser<Props>, ): GetInitialProps<Props> { return

    (context) => { const user = getUser() if (!user) { return redirect('/login', context.res) } if (getInitialProps) return getInitialProps(context, user) } } type GetInitialProps<Props> = ( context: NextPageContext, ) => Props | Promise<Props> type GetInitialPropsWithUser<Props> = ( context: NextPageContext, user: User, ) => Props | Promise<Props> 型は自作 引数で受け取った getInitialProps を最後に 実行する ユーザー認証を行う高階関数を作ってみる
  14. 23 export function withAuthenticated<Props>( getInitialProps?: GetInitialPropsWithUser<Props>, ): GetInitialProps<Props> { return

    (context) => { const user = getUser() if (!user) { return redirect('/login', context.res) } if (getInitialProps) return getInitialProps(context, user) } } type GetInitialProps<Props> = ( context: NextPageContext, ) => Props | Promise<Props> type GetInitialPropsWithUser<Props> = ( context: NextPageContext, user: User, ) => Props | Promise<Props> 型は自作 引数で受け取った getInitialProps を最後に 実行する ユーザー情報も渡す ユーザー認証を行う高階関数を作ってみる
  15. 24 const MyPage: NextPage<MyPageProps> = ({ name }) => {

    return <h1>Hello {name} !</h1> } MyPage.getInitialProps = withAuthenticated<MyPageProps>( async (_context, user) => { return { name: user.name } }, ) export default MyPage const MyPage: NextPage<MyPageProps> = ({ name }) => { return <h1>Hello {name} !</h1> } MyPage.getInitialProps = async (context) => { const user = await getUser() if (!user) { return redirect('/login, context.res) } return { name: user.name } } export default MyPage ユーザー認証を行う高階関数を作ってみる 比較
  16. 25 const MyPage: NextPage<MyPageProps> = ({ name }) => {

    return <h1>Hello {name} !</h1> } MyPage.getInitialProps = withAuthenticated<MyPageProps>( async (_context, user) => { return { name: user.name } }, ) export default MyPage const MyPage: NextPage<MyPageProps> = ({ name }) => { return <h1>Hello {name} !</h1> } MyPage.getInitialProps = async (context) => { const user = await getUser() if (!user) { return redirect('/login', context.res) } return { name: user.name } } export default MyPage ユーザー認証を行う高階関数を作ってみる 比較
  17. 26 const MyPage: NextPage<MyPageProps> = ({ name }) => {

    return <h1>Hello {name} !</h1> } MyPage.getInitialProps = withAuthenticated<MyPageProps>( async (_context, user) => { return { name: user.name } }, ) export default MyPage const MyPage: NextPage<MyPageProps> = ({ name }) => { return <h1>Hello {name} !</h1> } MyPage.getInitialProps = async (context) => { const user = await getUser() if (!user) { return redirect('/login', context.res) } return { name: user.name } } export default MyPage 認証処理をキレイに 切り出せた! ユーザー認証を行う高階関数を作ってみる 比較
  18. 27 getServerSideProps でも const MyPage: NextPage<PremiumPageProps> = ({ name })

    => { return <h1>Hello {name} !</h1> } export const getServerSideProps = withAuthenticated<MyPageProps>( async (_context, user) => { return { props: { name: user.name } } }, ) export default MyPage getStaticProps でも 🆗
  19. 28 getServerSideProps でも getStaticProps でも 🆗 const MyPage: NextPage<PremiumPageProps> =

    ({ name }) => { return <h1>Hello {name} !</h1> } export const getServerSideProps = withAuthenticated<MyPageProps>( async (_context, user) => { return { props: { name: user.name } } }, ) export default MyPage export function withAuthenticated<Props>( getServerSideProps: ( context: GetServerSidePropsContext, user: User, ) => Promise<GetServerSidePropsResult<Props>>, ): GetServerSideProps<Props> { return async (context) => { const user = await getUser() if (!user) { return { redirect: { destination: '/login', permanent: false } } } return await getServerSideProps(context, user) } }
  20. 30 • NewsPicks はフロントエンド基盤を Next.js へ移行中 🚀 • 現行環境と画面挙動を合わせたいのでデータフェッチ処理を活用している •

    Apollo Client のクエリキャッシュを使いたいので getInitialProps を採用している • データフェッチ処理を再利用したい場合は高階関数パターンが有効 まとめ