Imported from wwwyo/wwwyo (
AGENTS.md). Install upstream withnpx skills add wwwyo/wwwyo. Copyright stays with the author.
wwwyo.dev
個人の portfolio / blog サイト。成果物の一覧と、interactive なコンポーネント(tools の埋め込み・外部 API を叩くデモ等)を埋め込める記事を置く。
設計原則
- interactive 記事だけを置く: 静的テキストで済む記事は zenn / qiita / note に書く。ここには MDX + island でしか成立しない記事を置く(差別化)
- tools の埋め込みは iframe: wwwyo/tools のコードを import しない
- デフォルト zero-JS: 記事本文は静的 HTML。ブラウザで動かすコンポーネントだけ
client:visibleを付ける - 現行 works 一覧は置かない: 成果物は tools.wwwyo.dev へのリンクで済ませ、wwwyo.dev 側で portfolio データを持たない。ただし過去の個人プロジェクトのアーカイブは
/hobbyに静的な一覧 + 詳細ページとして置く
ディレクトリ構造
wwwyo.dev/
├── src/
│ ├── content/
│ │ └── blog/
│ │ ├── hello-world.mdx # island を持たない記事は単体ファイル
│ │ └── why-hdr-looks-brighter/ # island を持つ記事はフォルダ
│ │ ├── index.mdx
│ │ └── _components/ # この記事専用の island(collocation)
│ ├── components/ # 複数ページで共有するコンポーネント
│ └── pages/
├── mise.toml
└── wrangler.jsonc # Cloudflare Workers static assets
- 記事専用の island は記事と同じ場所に置く(collocation): 1記事でしか使わない island は
src/content/blog/<slug>/_components/に置き、MDX から./_components/Fooで相対 import する。記事の追加・削除がフォルダ単位で完結する。複数記事で共有するものだけsrc/components/に置く - island を持つ記事は
<slug>/index.mdx: glob loader は<slug>/index.mdxの id を<slug>に畳むため、URL は/blog/<slug>/のまま保たれる。<slug>/<slug>.mdxにすると id が<slug>/<slug>になり URL が変わるので使わない _components/の_prefix は「コンテンツではない」印: content collection の loader は**/*.mdxしか拾わないので.tsxが entry に混ざることはないが、Astro の慣習に合わせて underscore を付けている
情報設計
- blog URL:
/blog/[slug]/。slug はファイル名で日付は入れない(interactive 記事は寿命が長く、URL は短く保つ)。一覧はトップページ(/)。タグ・ページネーションは記事が増えるまで作らない - blog frontmatter:
title/description/pubDate(+ 必要になったらupdatedDate) - RSS:
/rss.xmlを@astrojs/rssで配信している - OGP: 当面はサイト共通の静的画像1枚。記事別 OGP は wwwyo/tools の satori +
@resvg/resvg-jsビルド時静的生成の知見を流用して、記事が増えてから導入 - hobby:
/hobbyに過去の個人プロジェクトを年の降順で一覧表示し、各プロジェクトは/hobby/[slug]/の詳細ページ(画像・動画・外部リンク)へリンクする。データはsrc/data/hobby.tsに集約する - リンクはページ冒頭に置く: 詳細ページの外部リンク・受賞などの重要情報はヘッダー直下(メディア・本文より上)に書く。読者は最後まで読まない前提でレイアウトする
セットアップ
ツールは mise で管理している。
mise install # mise.toml に従ってツールをインストール
bun install
bun run dev # 開発サーバー
bun run deploy # ビルド + Cloudflare Workers へ deploy
技術スタック
- Bun
- Astro + MDX + React(
@astrojs/react。island はclient:visibleを基本にする) - Tailwind CSS v4
- Cloudflare Workers static assets(Wrangler で deploy、custom domain: wwwyo.dev)
CSP(Content Security Policy)
- CSP は Astro の
security.cspで管理する(astro.config.mjs)。island hydration 等の inline script の hash 入り meta CSP をビルド時にページごとに生成する。public/_headersの CSP には meta で指定できないframe-ancestorsだけを置く(両方に directive を書くと積集合で評価されるため、script-src 等を header 側に足さないこと) - 記事・コンポーネントで
style=""属性を使わない: style-src に hash が含まれるため'unsafe-inline'はブラウザに無視される。装飾は CSS クラス(global.css)で行う。JS からのelement.style操作は CSP の対象外なので island 内の React は問題ない
パッケージ管理
- exact ピン留め(
bunfig.tomlのexact = true) - mise の tool 追加は
mise use --pin --before 7d <tool>@latest(supply-chain cooldown 7day)
