🎨
Next.jsで整える。デザインとロジックの分離
先日開催されたジャムジャムJamstackで登壇させていただいた時の記事になります。
簡易構成のリポジトリを作成しましたので、参考になればと思います。
経緯
- メディアサイトを作成することになった
- デザイナーコーダー ×1
- フロントエンドエンジニア ×1(ワイ)
- Next.jsとmicroCMSでSSGしてvercelにデプロイ
- 私的、Jamstack王道構成ですね
- デザイナーコーダーさん「Jsわからんです」「抵抗感あります」
- SSGをする為の
pages/
配下のファイルにはいろんな処理が記載されます。 -
getStaticProps
やgetStaticPaths
とか - ページネーションとかパンクズとか作る為の処理が色々記述されます。
- SSGをする為の
- この辺なんらかの方法で・いい感じに・分離できたら・いいですね^^
- デザイナーコーダーが触るViewの部分
- フロントエンドが触るロジックの部分
- これを目指していきます
pageExtensionsの活用
- nextの
pages/
配下のページとして読み込まれるファイルの拡張子などをカスタマイズできるというもの。- default値は
['tsx', 'ts', 'jsx', 'js']
- default値は
- これに
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
に記載していたgetStaticProps
やgetStaticPaths
などを.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やってるので、フォローしていただけたら嬉しいです!
参考
今回の構成を辿り着くにあたって非常に参考になりました。
Discussion