🎨

Next.jsで整える。デザインとロジックの分離

2021/10/01に公開

先日開催されたジャムジャムJamstackで登壇させていただいた時の記事になります。
https://speakerdeck.com/hanetsuki/next-dot-jstezheng-eru-tesaintorositukufalsefen-li

簡易構成のリポジトリを作成しましたので、参考になればと思います。
https://github.com/tsuki-lab/split-view-and-hook

経緯

  • メディアサイトを作成することになった
    • デザイナーコーダー ×1
    • フロントエンドエンジニア ×1(ワイ)
  • Next.jsとmicroCMSでSSGしてvercelにデプロイ
    • 私的、Jamstack王道構成ですね
  • デザイナーコーダーさん「Jsわからんです」「抵抗感あります」
    • SSGをする為のpages/配下のファイルにはいろんな処理が記載されます。
    • getStaticPropsgetStaticPathsとか
    • ページネーションとかパンクズとか作る為の処理が色々記述されます。
  • この辺なんらかの方法で・いい感じに・分離できたら・いいですね^^
    • デザイナーコーダーが触るViewの部分
    • フロントエンドが触るロジックの部分
    • これを目指していきます

pageExtensionsの活用

https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions

  • nextのpages/配下のページとして読み込まれるファイルの拡張子などをカスタマイズできるというもの。
    • default値は['tsx', 'ts', 'jsx', 'js']
  • これにpage.tsxという設定を加えました。
    • index.page.tsx, _app.page.tsx
  • 限定的なファイルのみページとして読み込むようになりました。

参考にpages/blog/[contentId].page.tsxを掲載します。

pages/blog/[contentId].page.tsx
import { microcmsClient } from '@/lib/microcms-client'
import type { GetStaticPaths, GetStaticPropsContext, InferGetStaticPropsType, NextPage } from 'next'

type Params = {
  contentId: string
}

const PageComponent: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (props) => {
  return (
    <div>
      <main>
        <h1>
          {props.title}
        </h1>
        <div dangerouslySetInnerHTML={{__html: props.body}} />
      </main>
    </div>
  )
}

export const getStaticProps = async(context: GetStaticPropsContext<Params>) => {
  const contentId = context.params?.contentId

  if (!contentId) throw Error('undefined contentId')

  const data = await microcmsClient.blog._contentId(contentId).$get()
  return {
    props: {
      ...data
    }
  }
}

export const getStaticPaths: GetStaticPaths<Params> = async() => {
  const data = await microcmsClient.blog.$get()
  const paths = data.contents.map(content => ({params: {contentId: content.id}}))

  return {
    paths,
    fallback: true
  }
}
export default PageComponent

ロジックの外出し.hook.tsの作成

  • .page.tsと同じ階層に.hook.tsを作成します。
    • pages/index.page.tsxだった場合、pages/index.hook.tsx
    • pages/blog/[contentId].page.tsだった場合、pages/blog/[contentId].hook.tsx
    • こんな感じ^^
  • .page.tsに記載していたgetStaticPropsgetStaticPathsなどを.hook.tsに移し替えます。
pages/blog/[contentId].hook.ts
import { microcmsClient } from '@/lib/microcms-client'
import type { GetStaticPaths, GetStaticPropsContext, InferGetStaticPropsType } from 'next'

type Params = {
  contentId: string
}

export type Props = InferGetStaticPropsType<typeof getStaticProps>

export const getStaticProps = async(context: GetStaticPropsContext<Params>) => {
  const contentId = context.params?.contentId

  if (!contentId) throw Error('undefined contentId')

  const data = await microcmsClient.blog._contentId(contentId).$get()
  return {
    props: {
      ...data
    }
  }
}

export const getStaticPaths: GetStaticPaths<Params> = async() => {
  const data = await microcmsClient.blog.$get()
  const paths = data.contents.map(content => ({params: {contentId: content.id}}))

  return {
    paths,
    fallback: true
  }
}
pages/blog/[contentId].page.tsx
import type { InferGetStaticPropsType, NextPage } from 'next'
import { getStaticPaths, getStaticProps, Props } from './[contentId].hook'

const PageComponent: NextPage<Props> = (props) => {
  return (
    <div>
      <main>
        <h1>
          {props.title}
        </h1>
        <div dangerouslySetInnerHTML={{__html: props.body}} />
      </main>
    </div>
  )
}

export default PageComponent
export { getStaticProps, getStaticPaths }
  • 今回はやっていませんが、他にもルーティング処理なども.hook.tsに分けてしまってもいいかもしれません。

まとめ

  • pageExtensionsでページとなるファイルの拡張子を制限することができる
    • そのおかげで同階層に.hook.tsというファイルを作成することができる
  • ⭕️ ビューとロジック分離することで、しっかり作業分担を行うことができる。コンフリクトも発生しにくい
  • ❌ 同階層にファイルを作成する為、ファイルが多くなればなるほど管理コストがかかる

最後に

Twitterやってるので、フォローしていただけたら嬉しいです!
https://twitter.com/hanetsuki_dev

参考

今回の構成を辿り着くにあたって非常に参考になりました。

https://zenn.dev/uttk/articles/d6be1c224494cb

https://zenn.dev/yuki0410/articles/2ad97915768826

https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions

Discussion