5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NewsPicksAdvent Calendar 2023

Day 9

Next.jsプロジェクトの設計改善を進める上で考えていたこと

Last updated at Posted at 2023-12-08

前置き

こんにちは!「NewsPicks」Web Experience Unit(以下Webチーム)の田端です。
この記事はNewsPicks アドベントカレンダー 2023の9日目の記事になります。
昨日はasaさんの【SwiftUI】文字を表示するベストプラクティス(Text・Label / TextEditor・TextField)でした!

背景

Web版「NewsPicks」では、リニューアルプロジェクトが2年ほど前から進んでおり、デザイン、基盤技術ともに大きく刷新が行われています。
フロントエンドの利用技術については「Spring + Thymeleaf + CoffeeScript + jQuery」から 「Nextjs + React + graphql + TypeScript」へと移行が行われています。
(詳しくは12月7日の鶴房さんのアドベントカレンダー記事で!)

リニューアルプロジェクトが始まって以来、5名前後のWebチームが主となって開発を進めてきましたが、ありがたいことにこの数ヶ月でWebチーム以外にもコミッターが大きく増え、プロジェクトの発足から2年以上を経たのも相まって設計の見直し、改善をしたいと感じる機会が増えてきました。

本題

Webチームの基本指針として、できるだけ技術の移り変わりについていきたい、必要に迫られる前に利用技術のバージョンアップに追従することで利用者、開発者とも体験を上げたいと考えています。

背景の流れもあり、Web版「NewsPicks」ではNext.jsのgetInitialPropsからgetServerSidePropsへ移行したい!という話をきっかけに絶賛設計変更&リファクタリング中です。

そもそもgetInitialPropsからgetServerSidePropsに移行する必要があるのか?

Next.js公式は、サーバーサイドとクライアントサイド両方で実行されるgetInitialPropsについて、今後getStaticPropsgetServerSidePropsを代わりに利用することを推奨しています。ページ表示に使うデータリクエストの要不要を明確にして2つを使い分け、パフォーマンスの向上を狙っています。
詳細は以下の公式ドキュメントを読んでいただければと思います。

この半年でWeb版NewsPicksの開発者がかなり増えた中で、「getServerSideProps使っていきたいですね!」という声をいただけた後押しもあり、今回の流れとなりました。

その中で_app.tsx内のgetInitialPropsに各ページ共通の処理をいくつか任せていることを再認識し、こちらの解消に取り組むことになりました。具体的には以下の課題と、その解決案を検討しました。

各ページ共通で必要な認証関連のリダイレクト処理が置かれていた

とある新規ページ開発時にはじめてgetServerSidePropsを導入した際、移行に伴ってこれまで_app.tsx内のgetInitialProps共通で行なっていた認証関連のリダイレクト処理を別箇所で行う必要があることに気づきました。

_app.tsx
App.getInitialProps = async (context: NextAppContext): Promise<AppProps> => {
    appProps = await NextApp.getInitialProps(context)
    const cookies = parseCookies(context.ctx)
    token = cookies["cookieKey"]
    
    // Cookieとユーザー情報次第でリダイレクト
    if(token && appProps.pageProps.userData){
        context.ctx.res.writeHead(302, {
          Location: "redirectURL"
        })
    }
}

middlewareで共通の認証処理を巻き取る

認証関連のリダイレクト処理を移動するにあたり、方針は3パターンありました。

①. 共通処理をutilとして作成し、各ページのgetServerSidePropsで呼び出す
②. _app.tsxApp内のuseEffectに共通処理を移す
③. middlewareで共通の認証処理を巻き取る

今回は

  • 認証処理でありmiddlewareへの配置に違和感ないこと
  • 各ページ実装する際に上記の内容を意識しなくていいため新規開発者の考慮すべきことを減らせること
  • 元々各pagePropsで共通処理のためのデータを渡す手間を感じていたこと
  • できればリダイレクト前に画面のチラつきを出したくない

といった理由から③の「middlewareで共通の認証処理を巻き取る」を選択しました。

_middleware.ts
export const middleware: NextMiddleware = async (request: NextRequest) => {
  const cookie = request.headers.get('cookie') || ''
  const token = request.cookies['cookie_key']
  const response = await fetch('URL', {
    method: 'GET',
    headers: { cookie },
  })

  // Cookieとユーザー情報次第でリダイレクト
  if (token && response.json().userData === 'hoge') {
    return NextResponse.redirect('redirectURL')
  }

  return NextResponse.next()
}

各ページ共通で使用するヘッダーに必要な情報を取得するクエリが叩かれていた

_app.tsx
  App.getInitialProps = async (context: NextAppContext): Promise<AppProps> => {
      const { data: headerData } =
        await apiClient.query<GetHeaderDataQuery>({
          query: GetHeaderDataDocument,
        })
      return {headerData}
  }

今回はヘッダーの情報でしたが、これ以外にも各ページで利用するデータを各ページで設定したGraphQLのQueryで取得しており、「新規ページを開発するにあたり各ページ共通で取得すべきデータを知っている必要がある」という状態は課題として持っていました。

samplePage.graphqls
query samplePage {
  samplePageData {
    ...samplePageData
  }

  userData {
    ...UserData
  }
  headerData {
    ...HeaderData
  }
  # And More!
}

各ページ共通で利用するデータをまとめたクエリを準備する

そこでヘッダーの情報取得を_app.tsxから剥がすのと同時に、各ページ共通で利用するデータをCommonDataとしてまとめ、新規ページにおいてはページ特有のQueryに併せてこれさえ呼べばいいという状態にしました。

CommonData.graphqls
# 各ページ共通で必要になるデータ

query GetCommonData {
  userData {
    ...UserData
  }
  headerData {
    ...HeaderData
  }
  # And More!
}

あとはgraphql-codegenで上記から生成されたQueryを各page.tsxで呼び出せば完了です。

hoge.page.tsx
  const { data: commonData } = await apiClient.query<CommonDataQuery>({
    query: CommonDataDocument
  })

これまでは各ページで必要なデータを1つ1つ記載していたので、かなり楽になりました。

設計改善に対する考え

本記事では

  • 技術の移り変わりにはEOLなど必要に迫られるなどの理由がなくとも追従していきたい
    • 追従に伴って設計の見直しを定期的に入れて開発者体験を上げたい
  • getInitialPropsからgetStaticProps/getServerSidePropsに移行する際は_app.tsx内のgetInitialPropsで共通化している処理を忘れないよう気をつけたい
  • 共通化している処理は各ページごとに処理を書き下すのではなく、新規開発者が意識せずとも実現される状態にしたい

といった話をさせていただきました。
こうしたいと思いつつも後回しにしがちな面があるので、定期的にリファクタリングしていきたいですね。

ここまでご覧いただきありがとうございました。
明日は私と同じくWebチームの西さんが投稿してくれます。お楽しみに!

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?