--- title: "vim-jp ラジオ オフィシャルサイト制作の舞台裏!" date: '2024-07-22' isPublished: true lang: 'ja' ---  https://vim-jp-radio.com/ > [!NOTE] > この記事は[Vim 駅伝](https://vim-jp.org/ekiden/)の 7/22 の記事です。 > また、この記事は デザインを担当した [輪ごむ](https://zenn.dev/wagomu) さんとの共同執筆記事です。 # はじめに 2024年7月8日月曜12時、ポッドキャストラジオ番組「[ エンジニアの楽園vim-jpラジオ ](https://vim-jp-radio.com/)」がAuDee(TOKYO FM)公式番組として配信開始されました。 https://blog.tomoya.dev/posts/vim-jp-radio/ https://github.com/vim-jp-radio/LP 本記事では、この vim-jp ラジオの Landing Page(LP) 制作の舞台裏を紹介します! デザインや技術的なポイントなど、様々な視点から制作のポイントを紹介します。 # デザインについて (by 輪ごむ) ## コンセプト ロゴから以下のようなイメージを膨らませてデザインを考えました。 - 夜 - バーみたい - わいわいとした雰囲気 - ネオン調 これらのイメージを頭の片隅に置いて大量の web サイトや LP を大量にインプットしてなんとかひねり出したのが今回完成した LP になります。 デザインのレビューをしてくださった tomoya さんありがとうございます!! 絵を描くことはありますが、初めての web デザインだったので、大変でしたがとっても楽しい経験ができました。 ## お気に入りポイント 背景アニメーションがお気に入りです。 夜の街で光が揺れているような雰囲気が出てお洒落かもと思って作ってみました。 当初は衝突するたびに少しずつ玉のスピードが早くなってしまう不具合がありましたが、ryoppippiさんが直してくれました。感謝!!! # 技術的なポイント (by ryoppippi) この LP の制作のために採用した技術的なポイントを紹介します。 Zenn なので技術的な話も少し深めに書いていきます。 今回 LP の制作にあたり、特に表示速度にはこだわりました。 - LP である以上、モバイルからのアクセスがある程度多いと予想されるため - LP は一般的に表示速度が遅くなりがちであるが、それに対して挑戦したかったため 結果として、 PageSpeed Insights では満足のいくスコアを得ることができました。  _[PageSpeed Insights のスコア](https://pagespeed.web.dev/analysis/https-vim-jp-radio-com/dacso4sz80?form_factor=mobile)_ それでは、技術的なポイントを見ていきましょう! ## Svelte5 / SvelteKit2 フレームワーク (FW) には Svelte5 および SvelteKit2 を採用しました。 https://kit.svelte.jp/ https://svelte-5-preview.vercel.app/docs/introduction チームメンバーが Svelte に興味を持っていたことが採用の決め手となりました。 今回のビルドは SSG (Static Site Generation) を採用し、SSR (Server Side Rendering) 等の機能は使用していません。 そのため、後述のように、 Cloudflare Pages では実質無料で運用することができます。 https://kit.svelte.jp/docs/adapter-static ### Svelte5 Svelte5 は現時点ではまだ RC 版ですが十分実用的でした。 今回は `runes `や `snippets` など Svelte5 で導入された新機能を積極的に使用しましたが、非常に書き心地が良かったです。 [`runes`](https://svelte-5-preview.vercel.app/docs/runes) はコンポーネントの引数の受け渡しや Reactivity の実装など、様々な場面で使用しました。型安全性が向上し、冗長な記述も減り、非常に快適に開発を進めることができました。 [`snippets`](https://svelte-5-preview.vercel.app/docs/snippets)はSvelteファイル内に繰り返し使うコードをまとめて置くことができる機能です。 Svelte4 までは同じような実装を使い回すには別ファイルにコンポーネントとして切り出す必要がありましたが、Svelte5ではファイル内にまとめて記述できるようになりました。 そのため、例えばレスポンシブデザインなどで同じようなコードを繰り返し書く場合に、より簡潔に実装することができました。 https://github.com/vim-jp-radio/LP/blob/b97bb019c15299ce3b2fbb2c9f4a7855bfbf7aae/src/routes/Platforms.svelte#L7-L43 ### enhanced-img 画像の最適化には、 [`enhanced-img`](https://kit.svelte.jp/docs/images) を使用しました。 `enhanced-img` は、スクリーンサイズに応じて適切な画像が選択されるように複数サイズの画像を自動生成したり、`png` などの画像を `webp` や `avif` に変換する機能を提供します。 こちらも preview 版であり、1ヶ月前の breaking change の影響で import 文でtype error が発生するなどの不具合はあるものの、機能の面では概ね問題なく使用することができました。 自動で画像の最適化を行ってくれるのは非常に便利で、パフォーマンス向上に大きく寄与しました。 ### favicon/metadata の自動生成 LP のサイトは favicon や metadata の設定が重要です。 ある程度は手作業で問題ありませんが、様々なサイズの favicon を用意するなど一部手作業では面倒な部分もあります。 そこで、favicon や metadata の生成を自動化するために `favicons` というライブラリを使用しました。 https://github.com/itgalaxy/favicons このライブラリは、設定に応じて metadata や 様々なサイズの favicon を生成してくれます。 今回は `favicons` を使用してビルド時に favicon や metadata を生成するための vite plugin を独自に作成、使用しました。 https://github.com/vim-jp-radio/LP/blob/b97bb019c15299ce3b2fbb2c9f4a7855bfbf7aae/vite.config.ts#L29-L46 この plugin では favicon や metadata を読み込むための `head` タグのコードも生成されます。今回は [`src/hooks.server.ts`](https://kit.svelte.jp/docs/hooks) を使用して、生成されたコードを `head` タグに埋め込みました。 https://github.com/vim-jp-radio/LP/blob/b97bb019c15299ce3b2fbb2c9f4a7855bfbf7aae/src/handles/meta.ts ### BudouX BudouXは、日本語や中国語のような、単語間にスペースを入れない言語のテキストを自然に改行するためのライブラリです。 これを使うことで、いわゆる [「あなたとJAVA 」現象](https://web.archive.org/web/20140221194840/https://www.java.com/ja/)と呼ばれる不自然な改行を防ぐことができます。 https://developers-jp.googleblog.com/2023/09/budoux-adobe.html 上のブログでも紹介されているように、BudouX は非常に便利なツールです。 しかし、そのままBudouX を実装に含めると約7KBのbundle cost および runtime cost が発生します。 この runtime costを避けるため、このLPの制作に合わせて新たな[Svelte Preprocessor](https://kit.svelte.jp/docs/integrations#preprocessors)を作成しました。 これにより、runtime cost を抑えつつ BudouX の機能を活用できます。 https://github.com/ryoppippi/svelte-preprocess-budoux 使い方は簡単です: 1. まず、`svelte.config.js` に preprocessor を追加します。 2. 次に、分かち書きが必要な要素に `data-budoux` 属性を追加します。 3. ビルドを実行すると、preprocessor が BudouX を使って分かち書きを行い、その結果を Svelte の markup 部分に直接埋め込みます。 https://github.com/vim-jp-radio/LP/blob/b97bb019c15299ce3b2fbb2c9f4a7855bfbf7aae/src/routes/Platforms.svelte#L8-L10  _ウィンドウの幅が変わっても単語が途中で途切れることなく適切に改行されている_ この方法により、runtime の overhead を増やすことなく、テキストを適切に改行できるようになりました。 添付の画像からわかるように、ウィンドウの幅が変わっても、テキストが自然に改行されています。 いい感じですね! ### URL Validation on build time このプロジェクトでは、URL が正しいものであるかをビルド時に検証しています。 SvelteKit には `building` や `dev` といった変数があり、これらが `true` の時にのみ実行されるコードを書くことができます。 今回は、`ensureURL` という関数を作成し、dev 環境および ビルド時のみ validation logic を実行するようにしました。 これらの validation logic は本番環境では tree-shaking の対象となり、bundle size に影響を与えません。 https://github.com/vim-jp-radio/LP/blob/2f9c28b4a4de4cd7be8db61a78ae04fd5bd08a03/src/lib/utils/url.ts ### SvelteKit-tweet  LP の 「リスナーの声」のセクションには、リスナーの皆さんからの投稿 (旧ツイート) を埋め込んでいます。 ~~この投稿の埋め込みには [`SvelteKit-tweet`](https://github.com/fayez-nazzal/sveltekit-tweet) を採用しました。~~ https://github.com/fayez-nazzal/sveltekit-tweet ~~このライブラリは [`react-tweet`](https://react-tweet.vercel.app/) の Svelte 版であり、 ビルド時に投稿の内容を取得してカードを生成します。~~ **追記: その後、こちらのライブラリをforkして、sveletweetというライブラリを使用することにしました。** https://github.com/ryoppippi/sveltweet これにより、bundle cost を最小限にし、 runtime cost をゼロにしながらも、見た目は公式のものと変わらない投稿カードを埋め込むことができます。 実際に devTools を起動して JavaScript を無効にしてみてください。投稿カードが問題なく表示されることが確認できるはずです。 ### Background Animation LP を開いてみると、背景にゆっくりと動くアニメーションが見えます。 こちらは Canvas を使用して実装しています(輪ごむさんが実装)。 https://github.com/vim-jp-radio/LP/blob/2f9c28b4a4de4cd7be8db61a78ae04fd5bd08a03/src/lib/Backgroud/circle.ts また、A11y への配慮として、[`prefers-reduced-motion`](https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-reduced-motion) を確認し、アニメーションの有無を切り替えています。 この media query は通常 CSS で使用されますが、今回は Animation の実装に JS を使用しているため、Svelte5 の新機能である `runes` を使用して reactive に media query の変化を監視しています。 https://github.com/vim-jp-radio/LP/blob/76c1826cab5745541f772ad56281c39d9b2c937e/src/lib/utils/runes.svelte.ts ## `UnoCSS` CSS の管理には `UnoCSS` を採用しました。 https://unocss.dev/ 最初は TailwindCSS を採用しようと思っていましたが、実は UnoCSS も良いという話を聞き、試しに採用してみました。 使い始めは戸惑いもありましたが、振り返ってみると採用して良かったと思います。 個人的 (ryoppippi的) に良いなと思ったのは、classの内部ではなく、属性に style を書くことができるところです。 TailwindCSS では全ての style を class に書くので、style が増えると class が複雑になりがちです。また、1行に複数の class を書くことが多いため、コードの見通しが悪くなりがちです。 UnoCSS では、html タグの属性として style を書くことができます。 また同じ種類の style はまとめて記述することもできるため、コードの見通しが良くなります。 https://github.com/vim-jp-radio/LP/blob/b97bb019c15299ce3b2fbb2c9f4a7855bfbf7aae/src/routes/Header.svelte#L6-L11 https://github.com/vim-jp-radio/LP/blob/2f9c28b4a4de4cd7be8db61a78ae04fd5bd08a03/src/routes/Personality.svelte#L25-L28 rule や shortcut も便利でした。 rule は生の CSS を、shortcut は複数の class を、 それぞれ一つの塊として定義することができます。 繰り返し登場するような style を簡潔に書く時には rule や shortcut が便利でした。 https://github.com/vim-jp-radio/LP/blob/2f9c28b4a4de4cd7be8db61a78ae04fd5bd08a03/uno.config.ts#L35-L42 個人的に一番役に立ったのは、UnoCSS Inspector です。  _UnoCSS Inspector_ https://unocss.dev/tools/inspector UnoCSS Inspector を用いると、ページごと、コンポーネントごとに、どの style が適用されているかを簡単に確認することができます。 このツールのおかげで不要な style を削除することができ、CSS の bundle size を削減することができました。 また、style がうまく適用されない時の debug にも大いに役立ちました。 個人的には UnoCSS は TailwindCSS の上位互換のように思えました。 今後も積極的に採用していきたいと思います。 > [!NOTE] > 今回、[属性に直接styleを書く記法](https://unocss.dev/presets/attributify) を採用しましたが、1つだけ注意点があります。 > > 以下は `uno.config.ts` の一部です。 > > ```ts > // uno.config.ts > > export default defineConfig({'\{'} > //... > presets: [ > // ... > presetAttributify({'\{'} prefix: 'uno-', prefixedOnly: true {'\}'}), > // ... > ], > > // ... > ){'\}'} > ``` > > 重要なのは、 `prefixedOnly: true` 、そして `prefix: 'uno-'` の設定です。 > これにより、`uno-` で始まる属性のみが style として処理されるようになります。 > > この設定を行わないと、以下のようなコードが書けてしまいます。 > > ```jsx >