自分のサイトを Next.js から Astro に移行した
2023/01/05
wattanxはじめに
年末から年始にかけて Next.js で作成した自分のサイトを Astro に移行しました。
以前は Next.js, Chakra UI を使用して作成していましたが、できるかぎり JS を減らしたサイトを作りたいと思って Astro に移行しました。
あとついでに Blog も作りました。
技術スタック
- Astro
- Tailwind
- Radix UI
- Partytown
- mdx
Astro めっちゃ楽
Partytown や sitemap の設定を integrations
に書くだけなので、めっちゃ楽で快適でした。
元々 tsx で書いていたコンポーネントをそのまま使えるので移行も簡単でした。(ハイドレーションするかどうかコンポーネント単位で選べるのが良い)
OG Image
OG Image の生成には satori を使用しました。
とりあえずビルド時に OG Image が動的に出力できるようにしています。(svg から png に変換するために sharp
を使っています。)
GitHub - vercel/satori: Enlightened library to convert HTML and CSS to SVG
Enlightened library to convert HTML and CSS to SVG - GitHub - vercel/satori: Enlightened library to convert HTML and CSS to SVG下記のようなスクリプトを用意してビルド時に OG Image を生成するようにしています。
import satori from "satori";
import sharp from "sharp";
import fs from "fs";
export const generateOgp = async (
options: { title: string; url: string }[],
) => {
for (const option of options) {
const png = await generateOgpImage(option.title);
fs.writeFileSync(`public/images${option.url}.png`, png);
}
};
const generateOgpImage = async (title: string) => {
// フォントデータを読み込む
const regular = fs.readFileSync("public/fonts/NotoSansJP-Regular.otf");
const medium = fs.readFileSync("public/fonts/NotoSansJP-Medium.otf");
// JSX から画像を生成する
const svg = await satori(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
backgroundImage: "linear-gradient(90deg, #00d2ff 0%, #3a47d5 100%)",
color: "#fff",
fontSize: 48,
fontWeight: 600,
justifyContent: "center",
alignItems: "center",
padding: "0 2rem",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
padding: "1rem 4rem",
backgroundColor: "#282b2f",
justifyContent: "space-between",
borderRadius: "1rem",
height: "90%",
}}
>
<p style={{ fontSize: 64, fontWeight: 700 }}>{title}</p>
<div style={{ display: "flex", alignItems: "center" }}>
<img
src="https://avatars.githubusercontent.com/wattanx"
width={80}
height={80}
style={{ borderRadius: "100%" }}
/>
<p style={{ marginLeft: "16px", fontWeight: 400 }}>wattanx</p>
</div>
</div>
</div>,
{
width: 1200,
height: 630,
fonts: [
{
name: "Noto Sans JP",
data: regular,
style: "normal",
weight: 400,
},
{
name: "Noto Sans JP",
data: medium,
style: "normal",
weight: 600,
},
],
},
);
// SVG から PNG 形式に変換する
const png = await sharp(Buffer.from(svg)).png().toBuffer();
return png;
};
Playground を確認しながら作成するとサクッと作ることができました。
つまずいたところ
Timeline を作成するにあたって、Tab の Component を作成する必要があったのですが、実装するのが手間なので Radix UI を使いました。
@radix-ui/react-tabs
を使用した状態でビルドした場合、下記のようなエラーが表示されてビルドできませんでした。
(yarn dev
は問題ない)
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
原因のソースコードは下記のような形で @radix-ui/react-tabs
を import しているところにありました。
import * as Tabs from "@radix-ui/react-tabs";
export const TabsRoot = Tabs.Root;
export const TabList = Tabs.List;
export const TabTrigger = Tabs.Trigger;
export const TabPanel = Tabs.Content;
それらしい Issue を見たが原因がわからず、Issue 内に書いてあった対処法 でひとまずビルドが通るようになりました。
対処法
import * as Tabs from "@radix-ui/react-tabs";
// これを入れる
const { List, Root, Trigger, Content } =
(Tabs as unknown as { default: typeof Tabs }).default || Tabs;
export const TabsRoot = Root;
export const TabList = List;
export const TabTrigger = Trigger;
export const TabPanel = Content;
最後に
今まで技術系の記事は Zenn に書いていたのでどう書き分ければいいか迷います。
できれば、継続的に発信していきたい。