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. Next.js のデータフェッチについて
    2021.11.24 【Next.js Update!】v12リリースを踏まえ、Next.jsの採用を考える
    株式会社 AlphaDrive
    Shuhei Akutagawa / 芥川 周平

    View Slide

  2. 2
    自己紹介
    芥川 周平
    2017.04 -
    受託開発の会社でWeb系の新規開発を4つほど経験
    2021.09 -
    株式会社アルファドライブ / 株式会社ニューズピックス に所属
    Web Frontend Unit で NewsPicks のリアーキテクチャを行っている
    兵庫県姫路市🏯からフルリモート
    @aku11i

    View Slide

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

    View Slide

  4. 4
    本日話すこと
    ● getInitialProps を使用している話
    ● データフェッチ処理を再利用したい話

    View Slide

  5. 5
    getInitialProps を使用している話

    View Slide

  6. 6
    NewsPicks のリアーキテクチャについて
    NewsPicks ではフロントエンドのリアーキテクチャを行っている
    ● モノリスな Web 基盤からフロントエンド部分を剥がし、
    Next.js に載せ替えている
    ● 初期段階ではページの見た目や機能はそのままに基盤だけを載せ換える方針
    ● 新基盤でも画面挙動を合わせたいので
    レンダリング前に必要なデータを全て取得しておく必要がある
    → getInitialProps or getServerSideProps を活用する

    View Slide

  7. 7
    現行基盤
    Webサーバー DB
    リクエストの度に
    HTML を構築して返却
    新基盤
    BFF
    getInitialProps
    or
    getServerSideProps
    NewsPicks のリアーキテクチャについて

    View Slide

  8. 8
    現行基盤
    Webサーバー DB
    リクエストの度に
    HTML を構築して返却
    新基盤
    BFF
    getInitialProps
    or
    getServerSideProps
    NewsPicks のリアーキテクチャについて
    getInitialProps を採用

    View Slide

  9. 9
    ● 初めは getServerSideProps を使用していた
    ● どのページでも必要になる共通データの BFF へのリクエストを抑えたい話が出た
    ○ ヘッダーに表示するユーザー情報など
    ● Apollo Client で GraphQL のクエリ結果をキャッシュするのがシンプルっぽい 👀
    ○ getServerSideProps では実現できなかった(詳細は次ページで解説)
    → getInitialProps を採用することに
    なぜ getInitialProps ?

    View Slide

  10. 10
    データフェッチと Apollo Client のキャッシュ について
    BFF
    BFF
    getInitialProps (初回)
    getServerSideProps
    getInitialProps (2回目以降)

    View Slide

  11. 11
    BFF
    BFF
    getInitialProps (初回)
    getServerSideProps
    getInitialProps (2回目以降)
    様々なユーザーのクエリを実行する
    のでキャッシュを利用できない
    データフェッチと Apollo Client のキャッシュ について

    View Slide

  12. 12
    キャッシュを利用できる
    → 一度実行したクエリは
    キャッシュからデータを取り出せる
    BFF
    BFF
    getInitialProps (初回)
    getServerSideProps
    getInitialProps (2回目以降)
    様々なユーザーのクエリを実行する
    のでキャッシュを利用できない
    データフェッチと Apollo Client のキャッシュ について

    View Slide

  13. 13
    ● 私の認識:
    ○ getInitialProps はサーバーとクライアントの両方で実行され、
    ■ Node.js と Browser に依存しないコードが求められる
    ■ クライアント向けのバンドルにデータフェッチ部分のコードが混入する
    ○ といった管理の難しさがある
    ○ 上記を解決するためにより良い選択肢として getServerSideProps /
    getStaticProps が登場
    ● 現行基盤の画面挙動と合わせつつキャッシュを使うために getInitialProps を選択
    ● 新規に設計をする場合はまず getServerSideProps / getStaticProps の使用を検討
    getInitialProps の立ち位置
    非推奨ではない
    getInitialProps を推奨するわけではないです。
    非推奨ではないのでまだ使えるという話です!

    View Slide

  14. 14
    データフェッチ処理を再利用したい話

    View Slide

  15. 15
    getInitialProps (getServerSideProps) 、膨らみがち🔥
    同じような処理が複数ページに散らばりがち
     → 複数ファイルに変更が及びがち
    データフェッチ処理を再利用したい

    View Slide

  16. 16
    getInitialProps (getServerSideProps) 、膨らみがち🔥
    同じような処理が複数ページに散らばりがち
     → 複数ファイルに変更が及びがち
    再利用できる形にしたい ♻
    目的:メンテしやすく、再利用しやすく
    高階関数(Higher-order function)にしてみる
    データフェッチ処理を再利用したい

    少なくとも以下のうち1つを満たす関数である。
    ● 関数(手続き)を引数に取る
    ● 関数を返す
    高階関数 - Wikipedia
    高階関数とは

    View Slide

  17. 17
    const MyPage: NextPage = ({ name }) => {
    return Hello {name} !
    }
    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 でもできます

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  27. 27
    getServerSideProps でも
    const MyPage: NextPage = ({ name }) => {
    return Hello {name} !
    }
    export const getServerSideProps = withAuthenticated(
    async (_context, user) => {
    return { props: { name: user.name } }
    },
    )
    export default MyPage
    getStaticProps でも 🆗

    View Slide

  28. 28
    getServerSideProps でも
    getStaticProps でも 🆗
    const MyPage: NextPage = ({ name }) => {
    return Hello {name} !
    }
    export const getServerSideProps = withAuthenticated(
    async (_context, user) => {
    return { props: { name: user.name } }
    },
    )
    export default MyPage
    export function withAuthenticated(
    getServerSideProps: (
    context: GetServerSidePropsContext,
    user: User,
    ) => Promise>,
    ): GetServerSideProps {
    return async (context) => {
    const user = await getUser()
    if (!user) {
    return { redirect: { destination: '/login', permanent: false } }
    }
    return await getServerSideProps(context, user)
    }
    }

    View Slide

  29. 29
    ● 重ね掛けすると実行時間が伸びる
    ○ async 関数が順に実行されるため
    ○ 関数内部で API リクエストをする場合は実行時間が伸びていく
    注意
    Page.getInitialProps = withAuth(withPremium(withAdmin()))

    View Slide

  30. 30
    ● NewsPicks はフロントエンド基盤を Next.js へ移行中 🚀
    ● 現行環境と画面挙動を合わせたいのでデータフェッチ処理を活用している
    ● Apollo Client のクエリキャッシュを使いたいので getInitialProps を採用している
    ● データフェッチ処理を再利用したい場合は高階関数パターンが有効
    まとめ

    View Slide

  31. 31
    最後に
    AlphaDrive/NewsPicks ではエンジニアを募集中です!
    https://recruit.alphadrive.co.jp/
    https://recruit.newspicks.com/
    今日の内容を詳しく知りたい方🖐カジュアルにお話ししましょう!
    https://meety.net/group_talks/oFZxAQWvdeLA

    View Slide