Nuxt.js から Next.js にブログを移行する
モチベーション
- 現在は Nuxt.js を使っているが、React のほうが使い慣れているので Next.js に置き換えたい
- webp への変換を手元でがんばっているが、next/image に任せたい
- typography.js を使ってるが、開発は止まっているようなので、tailwindcss-typography などに置き換えたい
- TypeScript で書きたい(当時はそこまで慣れてなかったのでJSで書いた)
これを参考にすればいけそう。
デプロイ先は今まで通り Netlify を使いたい。Next.js のサポートは手厚そうなので問題なさそう。
いままでは素の Markdown で書いていたが、 next/image などの利用を考えると MDX に変更したほうがよさそう。
画面レイアウトやデザインは、基本そのまま踏襲。
Next.js とは関係ないけど、OGPの画像を自動生成するやつは別で対応したい
Lighthouse のスコアは同じレベルを維持する
RSS フィードの生成を忘れずにやる
Syntax Highlighting は Prism を使おうとしたが、いいテーマがなかったので、以前のブログでも使っていた highlight.js を使うことに。
rehype プラグインがあったのでそれを使えばOK
sitemap.xml も生成する
atom.xml の生成に苦労したがなんとか完成。
結局使うことはなかったが、こちらの記事が勉強になった
After
atom.xml と sitemap.xml がエラーを吐く...
{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'esbuild'\nRequire stack:\n- /var/task/.netlify/functions-internal/next_atomxml/nextPage/chunks/637.js\n- /var/task/.netlify/functions-internal/next_atomxml/nextPage/pages/atom.xml.js\n- /var/task/.netlify/functions-internal/next_atomxml/next_atomxml.js\n- /var/task/next_atomxml.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js","trace":["Runtime.ImportModuleError: Error: Cannot find module 'esbuild'","Require stack:","- /var/task/.netlify/functions-internal/next_atomxml/nextPage/chunks/637.js","- /var/task/.netlify/functions-internal/next_atomxml/nextPage/pages/atom.xml.js","- /var/task/.netlify/functions-internal/next_atomxml/next_atomxml.js","- /var/task/next_atomxml.js","- /var/runtime/UserFunction.js","- /var/runtime/index.js"," at _loadUserApp (/var/runtime/UserFunction.js:100:13)"," at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)"," at Object.<anonymous> (/var/runtime/index.js:43:30)"," at Module._compile (internal/modules/cjs/loader.js:999:30)"," at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)"," at Module.load (internal/modules/cjs/loader.js:863:32)"," at Function.Module._load (internal/modules/cjs/loader.js:708:14)"," at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)"," at internal/main/run_main_module.js:17:47"]}
サポートフォーラムに投稿したら教えてもらった。 next-mdx-remote
と getServerSideProps
の組み合わせで発生するらしい...
next/image をMarkdown内に書きたかったために MDX を使おうと思っていたが、 Markdown 内には <img>
で書いて、React への変換時に next/image に書き換えるようにすれば、MDX にする必要がないので、次のように方針転換する。
- markdown に JSX は含ませない。(HTMLは含ませる)
- remark / rehype のプラグインを利用して Markdown から React への変換を行う
- その際に img タグを next/image に置き換える
export const ContentBody: React.FC<Props> = ({ mdx }: Props) => {
const processor = unified()
.use(remarkParse) // Parse markdown to mdast(markdown AST)
.use(remarkRehype, { allowDangerousHtml: true }) // mdast -> hast (HTML AST)
.use(rehypeRaw) // parse hast again (and raw nodes) to deal with HTML inside the Markdown
.use(rehypeHighlight) // syntax highlighting
.use(rehypeReact, {
createElement: React.createElement,
components: {
a: CustomLink,
img: Image,
},
}); // hast -> react
return processor.processSync(mdx).result;
};
atom.xml と sitemap.xml の生成は別のエラーでつまづいた。Netlify が自動生成する function から _post
が参照できないらしい。
{"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"Error: ENOENT: no such file or directory, scandir 'src/_posts'","trace":["Runtime.UnhandledPromiseRejection: Error: ENOENT: no such file or directory, scandir 'src/_posts'"," at process.<anonymous> (/var/runtime/index.js:35:15)"," at process.emit (events.js:314:20)"," at processPromiseRejections (internal/process/promises.js:209:33)"," at processTicksAndRejections (internal/process/task_queues.js:98:32)"]}
Netlify functions のためにディレクトリ構造をいじったりするのは嫌だし、インフラに依存したコードでハマるのは嫌なので、なんとか回避策を考える。
SSR をしようとすると Netlify functions でつまずくっぽいので、SSG で回避。
-
pages/atom.xml.tsx
ではなくpages/atom.tsx
にする。 -
atom.tsx
の SSG でpublic/atom.xml
を自動生成 -
/atom
にアクセスしたら、クライアントサイドで/atom.xml
にリダイレクト
export const getStaticProps = async () => {
const xml = await localPostRepository.generateAtomFeed();
await localPostRepository.writeAtomFeed(xml);
return {
props: {},
};
};
const AtomFeed: NextPage = () => {
const router = useRouter();
useEffect(() => {
router.replace('/atom.xml');
}, [router]);
return null;
};
export default AtomFeed;