Next.js でブログを作り直した
Yuji Matsumoto / October 19 2020
元々https://github.com/queq1890/blog というリポジトリで、Gatsby + Netlify な構成でブログを書いていたのだが、Next.js SSG + vercel での開発がどのような体験になるのかが気になり、ブログを作り直した。 移行までにやったこと、移行してみてどうだったかをまとめる。
*移行後のブログのコード: https://github.com/queq1890/shuho
目次
Next.js + mdx の環境を作る
Next.js の環境構築
Next.js 自体の設定はドキュメントを参考にしながら行った。
必要な dependency を install した上で、下記の script を package.json
に追記して、 yarn dev
を実行すれば開発用のサーバーが立ち上がる。
$ yarn add next react react-dom
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
TypeScript 対応
今回は TypeScript で開発を行いたかったので、その設定も同時に行った。 (ドキュメント)
空の tsconfig.json
を作成し、next dev
を実行すると、Next.js が自動で必要な設定を tsconfig.json
に書き込んでくれる。
ただし、これで設定完了という訳ではなく、next dev
は以下のような message と共に中断されてしまう。
$ touch tsconfig.json && yarn dev
# ...
Please install typescript, @types/react, and @types/node by running:
yarn add --dev typescript @types/react @types/node
# ...
TypeScript と、React / Node の型定義をインストールするように促されるので、指示どおりに以下の command を実行すると、.tsx
.ts
ファイルを Next.js プロジェクト上で扱えるようになる。
$ yarn add --dev typescript @types/react @types/node
mdx 対応
mdx を pages/
以下に配置できるようにする
pages/
以下に配置できるようにするNext.js はそのままだと .mdx
形式のファイルを読み込むことができないので、設定を行う必要がある。vercel 公式から @next/mdx
のようなライブラリも提供されているが、
今回は hashicorp 製の next-mdx-enhanced
を利用した。 next-mdx-enhanced
には layoutPath
という設定項目があり、mdx で記述された page component を読み込む際に、mdx を children に取る layout component を指定できるようになる。
例えば、以下のように next.config.js
を設定することで、全ての pages/**/*.mdx
を読み込む際に src/components/layouts/index.tsx
を layout component として読み込むようになる。
// next.config.js
const withMdxEnhanced = require('next-mdx-enhanced');
module.exports = withMdxEnhanced({
fileExtensions: ['mdx'],
layoutPath: 'src/components/layouts',
defaultLayout: true,
})({
webpack: (config, options) => {
// webpack 周りで変更したい設定があればここに書く
// loader の追加など
return config;
},
});
// src/components/layouts/index.tsx
import { FC } from 'react';
import { Flex } from '@chakra-ui/react';
import Header from 'components/molecules/Header';
import BlogSeo from 'components/molecules/BlogSeo';
import { FrontMatter } from 'types/models/post';
import BlogPostTitle from 'components/molecules/BlogPostTitle';
type Props = {
frontMatter: FrontMatter;
};
// children はparse されたmdx
// mdx で記述されたpage は常にこのlayout component と読み込まれるようになる
const MainLayout: FC<Props> = ({ children, frontMatter }) => {
const slug = frontMatter.__resourcePath
.replace('blog/', '')
.replace('.mdx', '');
return (
<>
<Header />
<BlogSeo
url={`https://shuho.queq1890.vercel.app/blog/${slug}`}
title={frontMatter.title}
summary={frontMatter.summary}
publishedAt={frontMatter.publishedAt}
image={frontMatter.image}
/>
<Flex direction="column" as="main" justifyContent="center" px={[4, 8]}>
<Flex
as="article"
direction="column"
justifyContent="center"
alignItems="flex-start"
m="0 auto 4rem auto"
maxWidth="700px"
w="100%"
>
<BlogPostTitle
title={frontMatter.title}
publishedAt={frontMatter.publishedAt}
/>
{children}
</Flex>
</Flex>
</>
);
};
export default MainLayout;
layout component 側では、parse された mdx を children
として、 mdx の frontMatter を frontMatter
として、props から受け取ることができるようになる。
MDXProvider の設定
ここまでの設定で、pages/
以下に配置した .mdx
ファイルを読み込むことができるようになったが、parse された HTML 上の各要素には何も style が当たっていない状態である。@mdx-js/react
の MDXProvider
を利用して、各要素が任意の React component として render されるように設定する。
MDXProvider
は全 page 共通で読み込まれるようにしたいので、pages/_app.tsx
に記述する。
$ yarn add @mdx-js/react
// MDXProvider に渡すcomponents
const MDXComponents = {
h1: (props) => <H1 {...props} />,
h2: (props) => <DocsHeading as="h2" size="lg" fontWeight="bold" {...props} />,
h3: (props) => <DocsHeading as="h3" size="md" fontWeight="bold" {...props} />,
h4: (props) => <DocsHeading as="h4" size="sm" fontWeight="bold" {...props} />,
h5: (props) => <DocsHeading as="h5" size="xs" fontWeight="bold" {...props} />,
h6: (props) => <DocsHeading as="h6" size="xs" fontWeight="bold" {...props} />,
inlineCode: (props) => (
<Code colorScheme="yellow" fontSize="0.84em" {...props} />
),
kbd: Kbd,
br: (props) => <Box height="24px" {...props} />,
hr: Hr,
table: Table,
th: THead,
td: TData,
a: CustomLink,
p: (props) => <Text as="p" mt={4} lineHeight="tall" {...props} />,
ul: (props) => <Box as="ul" pt={2} pl={4} ml={2} {...props} />,
ol: (props) => <Box as="ol" pt={2} pl={4} ml={2} {...props} />,
li: (props) => <Box as="li" pb={1} {...props} />,
blockquote: Quote,
};
export default MDXComponents;
// pages/_app.tsx
import { AppProps } from 'next/app';
import { MDXProvider } from '@mdx-js/react';
import MDXComponents from 'components/molecules/MDXComponents';
const MyApp = (props: AppProps) => {
const { Component, pageProps } = props;
return (
<>
<Head>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
</Head>
{/* MDXProvider のcomponents prop に定義したcomponents を渡す */}
<MDXProvider components={MDXComponents}>
<Component {...pageProps} />
</MDXProvider>
</>
);
};
export default MyApp;
移行した感想
- かなり簡素の設定で静的サイトを React.js で書くための環境が作れて良かった
- Gatsby.js より環境作るの楽 && フレームワーク特有の知識を要求されている感覚もなかったので、今後なにか静的サイトを作る時は Next.js で良さそう
- 上記では触れていないが、vercel への deploy 体験がかなり良かった
- terminal から
vercel
と実行するのみ - GitHub の repository と連携して自動 deploy
- PR level での preview など
- 別サービスからのドメインも config をポチポチしているだけで終わった
- terminal から
課題・その他やりたいこと
babel-plugin-import-glob-array
を捨てる- index page で全 mdx を取得して link を生成するのに
babel-plugin-import-glob-array
を利用している - どうしても TS の世界から
import { frontMatter as blogPosts } from './blog/*.mdx';
に型をつけるのが難しい getStaticProps
+fs
で書き換えれば良さそう
- index page で全 mdx を取得して link を生成するのに
- 画像の webp 対応
- 現在ブログに使っている画像は jpg or png
- lighthouse のスコアを上げようと思えば webp 化していく必要がある
- 画像を webp 化する script を書いて実行できるようにしておく?
- Theming
- 真っ白というのも味気ないので、ちゃんとデザイン決めたい
- atoms に相当する component を増やして storybook から確認できるようにしたい