目次
TL;DR
ポートフォリオを作り直した。
- 高速
- 拡張性
- ナウい
高速
フレームワークにNext.jsを採用してSGを行っている。
Vercelの犬になるのは悔しいが、我慢しよう。手軽にfontと画像の最適化を行ってくれるのは魅力だからだ。
SG とはいいながら、ページ遷移のたびに JS が走るので動きはなめらかだ(言い方を変えれば、ピュアじゃない)。
画像の管理にCloudinaryを用いて最適化された画像を配信している。
画像のサイズは表示速度に直結するのでとても助かる。
拡張性
全体を通して
コンポーネント駆動開発1と呼ばれるプラクティスに則ってAtomic Design(なんちゃって)なコンポーネント設計を意識したので再利用がしやすく拡張も楽だ。
Webページ
できるだけ情報を最新に保ちつつ、情報を変える際にこのリポジトリへ直接commitを残さないため、APIサーバーを建てそこを介して情報をやり取りした。
ブログ
Markdown(内容管理) + tsx(テンプレートエンジン)。Markdown ならそう簡単には廃れないだろうし、いつか別サービスにも投げ込める安心感があると、ラマヌジャンは言った(知らんけど)。
Markdownの処理系にはunified資産を用いた。
様々なunifiedのAPIを組み合わせて構築するから楽しかった。
ナウい
ナウい。そうだ、僕はナウいのが好きだ。下は僕が弊クラブのLTで話した際の資料を抜粋したもので、このポートフォリオに使われている技術の一覧である。
フロントエンド
フレームワークにNext.js、スタイリングにTailwind CSS(ただしCSS in JSで使いたかったから今回はtwin.macroを使用)。
GraphQLクライアントにUrqlを使っている。
ホスティング先はVercelだ(犬です、ワンワン)。
バックエンド
Juniperを使っている。お気づきだろうか?RustでGraphQLサーバーを書いてみている。
これがまた難しい。すぐにでもNest.jsあたりに逃げたい。
デプロイ先はrenderに任せっきりだ(Heroku?あいつは...)。
作ってて思ったがGraphQLは正義だ。
その他
アナリティクスに今回はumamiを使ってみている。めっちゃUIかわいくてお気に入りだ。
機能
以下では具体的に実装できた機能と使ったライブラリなどを書いていく。
Tailwind CSS & twin.macro
Tailwind CSSは正義だとは僕は思わない。だが解の一つではあると思う。
また今回Atomic Designを採用した関係で、CSS in JSでの開発が最適2であると思い、twin.macro
を用いた。
twin.macroには一つ大きな問題点があった。それはTailwind CSS v3に対応していないということだ。
↑一応issueは立っているため経過観察だ。
Tailwind CSS v3から追加されたflex-basis
などは当然サポートされていないため下のような処置を取った。
Urql
GraphQLのクライアントライブラリというと何を思いつくだろう。有名どころだとApolloやRelayがある。話が変わるが@next/bundle-analyzer
というライブラリがある。これでビルドして吐き出されたバンドルサイズを見てみよう。
きっと驚くだろう、ApolloやRelayのサイズの大きさを。それに比べてUrqlのサイズの小ささを。気になる人は下でUrqlが比較を行っている。
もちろん、適材適所ではある。今回はqueryだけでよかったのでなるべく小さいものにした。
/* pages/index.tsx */
// 略
// eslint-disable-next-line unicorn/prevent-abbreviations
export const getStaticProps: GetStaticProps<{ meta: SeoProperties; urqlState: SSRData }> = async () => {
const client = await urqlClient();
await client.query(HomeDocument).toPromise();
const meta: SeoProperties = {
description: "Rintaro Itokawa's Dev Site | re-taro",
ogImageUrl: encodeURI(`${OGP_HOST}/api/ogp?title=re-taro`),
pageRelPath: "",
pagetype: "website",
sitename: "re-taro.dev",
title: "Rintaro Itokawa - Emotion Seeker",
twcardtype: "summary_large_image",
};
return {
props: {
meta,
urqlState: ssrCache.extractData(),
},
};
};
const HomePage: NextPage<Properties> = ({ meta }) => {
const [response] = useQuery<HomeQuery>({ query: HomeDocument });
return <Home data={response.data} meta={meta} />;
};
export default withUrqlClient(
() => ({
url: END_POINT,
}),
{ neverSuspend: true, ssr: false },
)(HomePage);
少し変わった使い方をするがとてもシンプルで使いやすかった。
GitHub Flavored Markdown
remark-gfm
で対応した。
| 表を | 作る |
| -------- | ---------- |
| たとえば | このように |
| 要素を | 増やす |
https://re-taro.dev
みたいな生のリンクも置けるし
- こうやって
- リストが書ける。さらに、[^3]
表を | 作る |
---|---|
たとえば | このように |
要素を | 増やす |
みたいな生のリンクも置けるし。
- こうやって
- リストが書ける。さらに、3
絵文字
remark-gemoji
で変換する。
:v:
が✌️になる。
数式
remark-math
とrehype-katex
を噛ませる。
> $$
> f(x)=\sum^{\infty}_{k=0}f^{(k)}(0)\frac{x^k}{k!}
> $$
のようなインライン数式を記述できる。
スタイルシートを読み込むだけで設定が可能なので楽だ。
// components/organisms/post-meta/index.tsx
// 略
const PostMeta: React.FC<PostMetaPropeties> = ({ meta }) => (
<React.Fragment>
<Seo {...meta} />
<Head>
<link
rel={"stylesheet"}
href={"https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"}
integrity={"sha384-ljao5I1l+8KYFXG7LNEA7DyaFvuvSCmedUf6Y6JI7LJqiu8q5dEivP2nDdFH31V4"}
crossOrigin={"anonymous"}
/>
<title></title>
</Head>
</React.Fragment>
);
// 略
ルビ
@haxibamiさんが作成したremark-jaruby
を用いた。
> {水晶機巧-ハリファイバー}^(我らが母)
水晶機巧-ハリファイバー
以上を合わせたremark-parse
/ remark-rehype
まわりのメソッドチェーンが下の通り。
// utils/parser.ts
import rehypeShiki from "@re-taro/rehype-shiki";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeKatex from "rehype-katex";
import rehypeSlug from "rehype-slug";
import rehypeStringify from "rehype-stringify";
import remarkGemoji from "remark-gemoji";
import remarkGfm from "remark-gfm";
import remarkJaruby from "remark-jaruby";
import remarkMath from "remark-math";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import remarkToc from "remark-toc";
import remarkUnwrapImages from "remark-unwrap-images";
import * as shiki from "shiki";
import stripMarkdown from "strip-markdown";
import { unified } from "unified";
const MdToHtml = async (md: string) => {
const result = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkGemoji)
.use(remarkMath)
.use(remarkJaruby)
.use(remarkUnwrapImages)
.use(remarkToc, {
heading: "目次",
tight: true,
})
.use(remarkRehype)
.use(rehypeKatex)
.use(rehypeShiki, {
highlighter: await shiki.getHighlighter({ theme: "nord" }),
})
.use(rehypeSlug)
.use(rehypeAutolinkHeadings, {
behavior: "wrap",
})
.use(rehypeStringify)
.process(md);
return result.toString();
};
export { MdToHtml }
また、rehype-react
関連の処理は以下のようになる。
// lib/rehype-react.ts
import React from "react";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import type { Options as RehypeReactOptions } from "rehype-react";
import { unified } from "unified";
import { Image } from "~/components/molecules/image";
import type { ImageProperties } from "~/components/molecules/image";
import { Link } from "~/components/molecules/link";
import type { LinkProperties } from "~/components/molecules/link";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const RehypeReact = (html: string): React.ReactElement<unknown, string | React.JSXElementConstructor<any>> => {
const result = unified()
.use(rehypeParse, {
fragment: true,
})
.use(rehypeReact, {
components: {
// eslint-disable-next-line id-length
a: (properties: LinkProperties) => Link(properties),
img: (properties: ImageProperties) => Image(properties),
},
createElement: React.createElement,
} as RehypeReactOptions)
.processSync(html);
return result.result;
};
export { RehypeReact };
以上で非常に書きやすいブログになった。
動的OGP
@haxibamiさんの実装を見つつ、Vercelにデプロイした。
自動再デプロイ
データを置いているリポジトリが更新されるとactionsが発火してポートフォリオのリポジトリへdispatchを送りそれを受け取った方で自動ビルドが走る。なかなかシャレオツで気に入ってる。
ディスパッチを送る側。
name: Dispatch
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
curl -vv -H "Authorization: token ${{ secrets.DISPATCH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" "https://api.github.com/repos/re-taro/re-taro.dev/dispatches" -d '{"event_type": "update"}'
ディスパッチを受け取る側。
name: Dispatch production build
on:
repository_dispatch:
types: [update]
jobs:
// 実行したいもの
感想
めちゃくちゃいい仕上がりになったと自負している。
Footnotes
-
↩コンポーネント駆動開発について調べた - Qiita今までの開発手順 コンポーネント単位で開発してきた。アトミックデザインを念頭に開発し、各コンポーネントを組み立て、一枚のページを構築させまた、ユニットテストた結合テストといったバグを早期に発見できるように開発してきたが、タイトル...https://qiita.com/UCLab1421/items/1c4e4acfdc785dbfa269コンポーネント駆動開発について調べた - Qiita今までの開発手順 コンポーネント単位で開発してきた。アトミックデザインを念頭に開発し、各コンポーネントを組み立て、一枚のページを構築させまた、ユニットテストた結合テストといったバグを早期に発見できるように開発してきたが、タイトル...https://qiita.com/UCLab1421/items/1c4e4acfdc785dbfa269
-
↩君のemotionを解き放て!-CSSinJSの歩き方-https://zenn.dev/t_keshi/articles/emotional-usage-of-emotion君のemotionを解き放て!-CSSinJSの歩き方-https://zenn.dev/t_keshi/articles/emotional-usage-of-emotion
-
脚注も使える。 ↩