
はじめに
2024年7月8日月曜12時、ポッドキャストラジオ番組「エンジニアの楽園vim-jpラジオ」が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 では満足のいくスコアを得ることができました。
それでは、技術的なポイントを見ていきましょう!
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 はコンポーネントの引数の受け渡しや Reactivity の実装など、様々な場面で使用しました。型安全性が向上し、冗長な記述も減り、非常に快適に開発を進めることができました。
snippetsはSvelteファイル内に繰り返し使うコードをまとめて置くことができる機能です。
Svelte4 までは同じような実装を使い回すには別ファイルにコンポーネントとして切り出す必要がありましたが、Svelte5ではファイル内にまとめて記述できるようになりました。
そのため、例えばレスポンシブデザインなどで同じようなコードを繰り返し書く場合に、より簡潔に実装することができました。
enhanced-img
画像の最適化には、 enhanced-img を使用しました。 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 を独自に作成、使用しました。
この plugin では favicon や metadata を読み込むための head タグのコードも生成されます。今回は src/hooks.server.ts を使用して、生成されたコードを head タグに埋め込みました。
https://github.com/vim-jp-radio/LP/blob/b97bb019c15299ce3b2fbb2c9f4a7855bfbf7aae/src/handles/meta.ts
BudouX
BudouXは、日本語や中国語のような、単語間にスペースを入れない言語のテキストを自然に改行するためのライブラリです。 これを使うことで、いわゆる 「あなたとJAVA 」現象と呼ばれる不自然な改行を防ぐことができます。
https://developers-jp.googleblog.com/2023/09/budoux-adobe.html
上のブログでも紹介されているように、BudouX は非常に便利なツールです。 しかし、そのままBudouX を実装に含めると約7KBのbundle cost および runtime cost が発生します。
この runtime costを避けるため、このLPの制作に合わせて新たなSvelte Preprocessorを作成しました。 これにより、runtime cost を抑えつつ BudouX の機能を活用できます。
https://github.com/ryoppippi/svelte-preprocess-budoux
使い方は簡単です:
- まず、
svelte.config.jsに preprocessor を追加します。 - 次に、分かち書きが必要な要素に
data-budoux属性を追加します。 - ビルドを実行すると、preprocessor が BudouX を使って分かち書きを行い、その結果を Svelte の markup 部分に直接埋め込みます。
ウィンドウの幅が変わっても単語が途中で途切れることなく適切に改行されている
この方法により、runtime の overhead を増やすことなく、テキストを適切に改行できるようになりました。 添付の画像からわかるように、ウィンドウの幅が変わっても、テキストが自然に改行されています。 いい感じですね!
URL Validation on build time
このプロジェクトでは、URL が正しいものであるかをビルド時に検証しています。
SvelteKit には building や dev といった変数があり、これらが true の時にのみ実行されるコードを書くことができます。
今回は、ensureURL という関数を作成し、dev 環境および ビルド時のみ validation logic を実行するようにしました。
これらの validation logic は本番環境では tree-shaking の対象となり、bundle size に影響を与えません。
SvelteKit-tweet

LP の 「リスナーの声」のセクションには、リスナーの皆さんからの投稿 (旧ツイート) を埋め込んでいます。 この投稿の埋め込みには SvelteKit-tweet を採用しました。
https://github.com/fayez-nazzal/sveltekit-tweet
このライブラリは react-tweet の Svelte 版であり、 ビルド時に投稿の内容を取得してカードを生成します。
追記: その後、こちらのライブラリをforkして、sveletweetというライブラリを使用することにしました。
https://github.com/ryoppippi/sveltweet
これにより、bundle cost を最小限にし、 runtime cost をゼロにしながらも、見た目は公式のものと変わらない投稿カードを埋め込むことができます。 実際に devTools を起動して JavaScript を無効にしてみてください。投稿カードが問題なく表示されることが確認できるはずです。
Background Animation
LP を開いてみると、背景にゆっくりと動くアニメーションが見えます。 こちらは Canvas を使用して実装しています(輪ごむさんが実装)。
また、A11y への配慮として、prefers-reduced-motion を確認し、アニメーションの有無を切り替えています。
この media query は通常 CSS で使用されますが、今回は Animation の実装に JS を使用しているため、Svelte5 の新機能である runes を使用して reactive に media query の変化を監視しています。
UnoCSS
CSS の管理には UnoCSS を採用しました。
最初は 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 が便利でした。
個人的に一番役に立ったのは、UnoCSS Inspector です。
UnoCSS Inspector https://unocss.dev/tools/inspector
UnoCSS Inspector を用いると、ページごと、コンポーネントごとに、どの style が適用されているかを簡単に確認することができます。 このツールのおかげで不要な style を削除することができ、CSS の bundle size を削減することができました。 また、style がうまく適用されない時の debug にも大いに役立ちました。
個人的には UnoCSS は TailwindCSS の上位互換のように思えました。 今後も積極的に採用していきたいと思います。
Note
今回、属性に直接styleを書く記法 を採用しましたが、1つだけ注意点があります。
以下は uno.config.ts の一部です。
// uno.config.ts
export default defineConfig({'\{'}
//...
presets: [
// ...
presetAttributify({'\{'} prefix: 'uno-', prefixedOnly: true {'\}'}),
// ...
],
// ...
){'\}'}
重要なのは、 prefixedOnly: true 、そして prefix: 'uno-' の設定です。
これにより、uno- で始まる属性のみが style として処理されるようになります。
この設定を行わないと、以下のようなコードが書けてしまいます。
<div
bg-red-500
mb-4
/>;
このコードでも大抵の場合は問題ありませんし、公式の Document でも採用されている記法です。
しかし、将来的にこの記法が問題になる可能性があります。
例えば、mb のような属性が規格として追加された場合、既存のコードが意図しない挙動をする可能性があります。
そのため、書き手に uno- の prefix を付けるように強制することで、将来的な問題を回避することができます^参考: https://dev.to/owlnai/writing-future-proof-unocss-5b68。
Cloudflare Pages
デプロイ環境は、Cloudflare Pages を採用しました。
- 基本無料
- 速い
- Web Analytics もいい感じに使える
- CD 環境もある
といいことづくめでした。
前述のように、本プロジェクトではビルド手法として SSG を採用しているため、Cloudflare Workers を一切使用していません。 そのため、基本的に無料で運用することができます。
Redirect
今回のプロジェクトでは特定の Path に対して redirect を設定し、短縮 URL のように使用しています。
例えば https://vim-jp-radio.com/apple にアクセスすると、Apple Podcast のページに redirect されます。
実は、Cloudflare Pages では _redirects ファイルを一緒に deploy することで、Cloudflare Workers を使用することなく redirect を設定することができます。
https://developers.cloudflare.com/pages/configuration/redirects/
しかし、この _redirects ファイルの内容を手動で管理するのは面倒です。
また、開発環境(vite) は標準では _redirects ファイルを認識しないため、手元で redirect を確認することができません。
そこで、vite-plugin-cloudflare-redirect を作成しました。 https://github.com/ryoppippi/vite-plugin-cloudflare-redirect
この plugin は以下の機能を提供します。
vite.config.tsに記述した設定から_redirectsファイルを生成する- 開発時やプレビュー時の vite server で動作する middleware を提供し、手元でも redirect を確認することができる
これにより _redirects ファイルの管理を簡略化し、local で redirect を確認できるようになりました。
Easter Eggs 🐰🥚
実はこの LP にはイースターエッグが仕込まれています。 ソースコードを読み解くのもよし、ブラウザの devTools で謎解きをするのもよし! ぜひ探してみてください!!
もし見つけられたら X や Bluesky で ハッシュタグ #vimjpradio をつけて投稿してみてください。
おわりに
vim-jp ラジオの LP を制作しました。
我々自身も vim-jp ラジオをリスナーとしてもとても楽しみにしています。 とても面白い番組なので、ぜひ聞いてみてください。
P.S. vim-jp について
vim-jp ラジオで vim-jp に興味を持った方は、ぜひ vim-jp に参加してみてください。
またいくつか vim-jp 入門?なる記事があるので、ぜひ読んでみてください。 https://blog.tomoya.dev/posts/vim-jp-is-a-paradise-for-engineers/ https://wagomu.me/blog/2024-06-26-vim-ekiden/