Closed15

Migrate from Next.js to Astro

chocolatchocolat

Next.js製のブログメインのサイトをAstroに移行する作業のメモ
2025/02 加筆・修正

chocolatchocolat

概要

Must Have

  • ブログはmarkdown
  • カテゴリー、タグあり
  • 本文にコードブロック、目次、見出しのリンク
  • 多言語

Nice to Have

  • CodePenなどの埋め込み対応
  • 動的にOG Imageを生成
  • markdown内のリンクを内部/外部に対応
  • rss
  • Search form
  • CMS
chocolatchocolat

Astro開発環境を整える

1. Astroのインストール

一旦フォルダ内を空にしてAstroの開発環境を整える

npm create astro@latest

A basic, minimal starter (recommended) を選択

インストールが完了後、localでの表示を確認

2. ESLint, Prettier の設定

/* ESLint with TypeScript */
npm install --save-dev eslint eslint-plugin-astro
npm install --save-dev @typescript-eslint/parser

/* Prettier */
npm i --save-dev prettier prettier-plugin-astro

個人的に追加

/* For ESLint */
npm install --save-dev @typescript-eslint/eslint-plugin
npm install --save-dev eslint-plugin-import

/* For Prettier */
npm install --save-dev eslint-config-prettier

各種設定ファイルを既存から移動(一部Astro用に書き換える)

  • .eslintignore
  • .eslintrc.json(React用のルールは一旦排除)
  • .prettierignore
  • .prettierrc(以下を追加)
{
  ...
  "plugins": ["prettier-plugin-astro"],
  "overrides": [
    {
      "files": "*.astro",
      "options": {
        "parser": "astro"
      }
    }
  ]
}

https://docs.astro.build/en/editor-setup/#other-tools
https://ota-meshi.github.io/eslint-plugin-astro/user-guide/

3. React

npx astro add react

https://docs.astro.build/en/guides/integrations-guide/#automatic-integration-setup

4. importのパスを絶対パスにする

tsconfig.json に以下を追加する

{
  ...
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

5. リセットCSS, SCSS

npm install sass

npm install modern-normalize

既存のファイルを移動して読み込む

// Layout.astro

---
import "node_modules/modern-normalize/modern-normalize.css";
import "@/styles/global.scss";
---
chocolatchocolat

その他

静的ファイルを移動

  • favicon.ico → public/
  • robots.txt → public/
  • manifest.json → public/

Google Analytics

ライブラリをインストール

npx astro add partytown
chocolatchocolat

多言語化対応

Next.jsとの大きな違いは /pages/ 以下
Next.jsのときは基本的にcontents以外のファイルは言語ごとに持っている必要がなかったが、Astroでは /pages/ 配下も全て言語に対応したファイルが必要。

デフォルト言語でURLに /ja/ が入るのを許容すればDynamic routesでファイルを共通化できそう。ただデフォルト言語はprefix入れたくない。/にアクセスしたら /ja/にリダイレクトさせる形で共通化させるので、/ja/を/にリダイレクトはできない。

コンテンツ部分をまるっとコンポーネント化して、pages 内の各言語ファイルではそれを読み込むだけにする。


公式のドキュメントに沿って設定する
https://docs.astro.build/en/recipes/i18n/

https://docs.astro.build/ja/guides/integrations-guide/sitemap/#i18n

chocolatchocolat

Layout.astro

Next.js では以下のファイルに記述されている主に <head> 内のものとheader, footerの読み込み

  • src/pages/_document.tsx
  • src/pages/_app.tsx
  • src/components/Layout.tsx (全ページで読み込まれるコンポーネント)
chocolatchocolat

Todo

  • Elements
    • Accordion
    • Ads
    • LocaleSwitcher => spec out
    • ScrollTo → JSで書き直す
    • Share
  • Head
    • SEO.tsx → BaseHeadにする
  • Layout
    • Footer
    • Header
    • Layout
    • Navigation → headerのみで使用のため削除
  • Page → 削除
    • ContentBlock
    • Title
  • Post
    • CategoryList
    • PostDate
    • PostDetail
    • PostFooter
    • PostListing
    • PostNoTranslate
    • PostTags
    • TagList
  • Project → 削除
    • ProjectCard
    • ProjectList
  • Template → 削除
    • TemplateCard
    • TemplateList
  • User
    • UserInfo
    • UserLogo
chocolatchocolat

メモ

React.FragmentFragment に置き換え可

childrenへのcss

Reactで以下のようにchildrenへのcssを親コンポーネント側で指定している場合、
childrenのdivに width: 50% は適用される

<div css={list}>
  {children}
</div>

const list = css`
  & > div {
    width: 50%;
  }
`;

Astroへの書き換え後
<slot /> のdivへスタイルは適用されない

<div class="list">
  <slot />
</div>

<style>
.list {
  & > div {
    width: 50%;
  }
}
</style>
chocolatchocolat

一部コンポーネントにReactを使う

npx astro add react
npm install @astrojs/react

astro.config.mjs を自動的に書き換えるか確認があるので(Y)を選択

Reactコンポーネントを読み込む

---
import SomeComponent from "../SomeComponent.tsx";
---

<SomeComponent client:visible />
chocolatchocolat

JavaScript

cssと同じ要領で <script></script> 内に記述する

<button id="button">
ボタン
</button>

<script>
  const button = document.getElementById("button");
  button?.addEventListener("click", () => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  });
</script>
chocolatchocolat

tsxからastroへの書き換え

基本的に必要な書き換え

import を --- で挟む

html
+ ---
import { Card } from "@/src/components/Card.astro";
+ ---

style(CSS)

  • className を class に書き換え
  • cssの記述を <style></style> に移動(CSS-in-JSの場合)
html

- <div className="card">
+ <div class="card">
  ...

+ <style>
+   .card {
+     ...
+   }
+ </style>

propsとReact関連の書き換え

  • React 関連や不要なimportを削除
  • interface でpropsの型を定義
  • const でコンポーネント内で使用するpropsを定義
  • return () 内のhtml以外を削除
html
---
- import { ReactElement, ReactNode } from "react";
...
+ interface Props {
+   title: string;
+ }

+ const { title } = Astro.props;
---

- const Card = ({ title }: Props): ReactElement => {
-   return (
    <div>{title}</div>
-   );
- }

- export default ProjectList;
chocolatchocolat

markdownファイルの読み込み

Next.js

  • markdownファイルのディレクトリを取得
  • 表示に必要なデータを取得
  • 並べ替え
  • 件数絞り込み
    をそれぞれJavaScriptで書いていたのをAstroでは
    上2つは getCollection() のみで置き換えられるのでかなり記述が少なく
chocolatchocolat

サイトマップを追加

npx astro add sitemap

configにサイトURLを追加(追加しない場合sitemap-index.xmlは生成されない)

// astro.config.mjs
...
import sitemap from "@astrojs/sitemap";

export default defineConfig({
  site: "https://sample.com",
  integrations: [sitemap()],
});

npm run build でdist内にxmlファイルが作成されるのを確認
Layout.astroで読み込み

// Layout.astro

...
<head>
  <link rel="sitemap" href="/sitemap-index.xml" />
</head>
chocolatchocolat

サポートされていない画像

投稿記事内で読み込んでいたgif画像がlocalでは問題ないが、デプロイするとエラーになるため削除した

このスクラップは2025/02/11にクローズされました