🚀

SvelteKit でポートフォリオサイトを作ってみる

2024/02/23に公開

Introduction

会社で Svelte を積極的に採用していく流れになったため、個人でも触ってみたいと思い SvelteKit を使ってポートフォリオサイトを作ってみました。完成したものは以下になります。
https://yuzoiwasaki.me/

ソースはこちら
https://github.com/yuzoiwasaki/yuzoiwasaki.me

ちなみに、今回のデザインとマークアップは基本的に ChatGPT にお願いしてみました。便利ですね。個人のポートフォリオサイトレベルなら全然これで十分という感じ。

あとはデプロイに Vercel を使ってみたりと、今回の個人開発でも気になっていたがちゃんと触ったことがなかった技術を使ってみました。Vercel も良かったです。めっちゃ楽。

Svelte とは

Svelte とは Rich Harris によって作り出されたユーザーインターフェースを構築するための JavaScript フレームワークの一つです。いわゆる React とか Vue.js と並べて語られることが多いですが、これら2つとの決定的な違いは Svelte が仮想DOMを用いない「コンパイラ」であることです。ブラウザにロードされるタイミングではなく、ビルドされる段階でコードが評価されるため、より高速な処理が可能になっています。また、公式サイトにもあるように、React などと比べてより少ない行数でシンプルに処理を実現できるのも特徴となっています。

SvelteKit とは

SvelteKit は2022年に Svelte コアチームによってリリースされた Webフレームワークであり、React や Vue.js でいうところの Next.js や Nuxt にあたります。また、これらが別々のチームによって開発されている一方で Svelte と SvelteKit は同じチームが開発しています。SvelteKit は命名規則などがやや特殊ではありますが、慣れるととても使いやすいフレームワークです。

作ってみる

まずはプロジェクトの雛形を作ります。

npm create svelte@latest yuzoiwasaki.me

いろいろ聞かれますが、Which Svelte app template? には Skeleton project を選択してください。TypeScript や Lint の設定はお好みで。

$ npm create svelte@latest yuzoiwasaki.me
Need to install the following packages:
create-svelte@6.0.9
Ok to proceed? (y)

create-svelte version 6.0.9

┌  Welcome to SvelteKit!
│
◇  Which Svelte app template?
│  Skeleton project
│
◇  Add type checking with TypeScript?
│  Yes, using JavaScript with JSDoc comments
│
◇  Select additional options (use arrow keys/space bar)
│  none
│
└  Your project is ready!

✔ Type-checked JavaScript
  https://www.typescriptlang.org/tsconfig#checkJs

Install community-maintained integrations:
  https://github.com/svelte-add/svelte-add

Next steps:
  1: cd yuzoiwasaki.me
  2: npm install
  3: git init && git add -A && git commit -m "Initial commit" (optional)
  4: npm run dev -- --open

To close the dev server, hit Ctrl-C

Stuck? Visit us at https://svelte.dev/chat

ディレクトリ構成としてはこんな感じです。

yuzoiwasaki.me $ tree
.
├── README.md
├── jsconfig.json
├── package.json
├── src
│   ├── app.d.ts
│   ├── app.html
│   ├── lib
│   │   └── index.js
│   └── routes
│       └── +page.svelte
├── static
│   └── favicon.png
├── svelte.config.js
└── vite.config.js

5 directories, 10 files

作られたプロジェクトに移動して npm install && npm run dev -- --open ですぐに開発用サーバを立ち上げることができます。

yuzoiwasaki.me $ npm install

added 109 packages, and audited 110 packages in 15s

11 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
yuzoiwasaki.me $ npm run dev -- --open

> yuzoiwasaki.me@0.0.1 dev
> vite dev --open

 VITE v5.1.4  ready in 354 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

上記 src ディレクトリの中の routes/+page.svelte がメインのファイルとなり、この状態でトップにアクセスするとこのファイルが読み込まれます。ですので、基本的にはここにモリモリコードを書いていけばOKです。とはいえ、それだとあまり綺麗ではないので、最終的にはコンポーネント化したりライブラリ化するなどして適切な粒度に切り出すと良いでしょう。

今回作ったポートフォリオサイトの最終的な中身は以下になりました。

src/routes/+page.svelte
<script>
  import profile from "$lib/images/profile.jpg";
  import { page } from "$app/stores";
  import Header from "./Header.svelte";
  import AboutMe from "./AboutMe.svelte";
  import Experience from "./Experience.svelte";
  import Skill from "./Skill.svelte";
  import Article from "./Article.svelte";
  import Talk from "./Talk.svelte";
  import Contact from "./Contact.svelte";
  import Footer from "./Footer.svelte";

  export let data;

  const { skills, articles, talks } = data;

  const title = "Yuzo Iwasaki のポートフォリオサイト";
  const description =
    "エンジニア歴15年程度のベテランエンジニアです。ソフトウェアエンジニアリングをはじめ、SREやマネジメント、採用、IT統制など幅広く関わってきました。HRTを大切にする優しいチームが好きで、自らがマネージャーの際はそのようなチーム作りを心掛けています。";
</script>

<svelte:head>
  <title>{title}</title>
  <meta name="description" content={description} />
  <meta property="og:type" content="website" />
  <meta property="og:url" content={$page.url} />
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:image" content={profile} />
</svelte:head>

<Header />

<AboutMe />

<Experience />

<Skill {skills} />

<Article {articles} />

<Talk {talks} />

<Contact />

<Footer />

<style>
  :global(body) {
    font-family: "Arial", sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
  }

  :global(section) {
    max-width: 800px;
    margin: 2em auto;
    padding: 2em;
    background-color: #fff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  }

  :global(h2) {
    color: #333;
  }

  :global(p) {
    line-height: 1.6;
    color: #555;
  }

  :global(a) {
    color: #007bff;
    text-decoration: none;
  }

  :global(a:hover) {
    text-decoration: underline;
  }
</style>

ヘルパーモジュール

SvelteKit には便利なヘルパーモジュールがいくつか存在します。上のコードでいうと、 import { page } from "$app/stores"; がそうです。今回はページのURLを取得するために使っていますが、それ以外にも便利なヘルパーモジュールが多数存在しています。

export let data

Svelte における export let foo は通常の JavaScript における export とはやや異なり、この変数の値をコンポーネントの外から設定できるようにするという意味を持ちます。また、 data というのは SvelteKit のプロパティで、ルートへのアクセスがあった場合に自動的に load 関数をサーバーサイドで実行し、その戻り値を data という名前のプロパティとしてページコンポーネントに渡します。

イメージとしては +page.server.js に書かれた load 処理が最初に走り、それを +page.svelte で読み込んでデータとして使っている感じです。 +page.server.js+page.svelte と同階層におき、このルートに紐づくサーバーサイド処理を記述する際に使用します。

コンポーネント

React や Vue.js 同様、 Svelte でももちろんコンポーネントを使うことができます。直感的ですね。

<svelte:head>

<svelte:head> 内に書かれた処理は head ダグの中に収納されます。大元は src/app.html%sveltekit.head% です。

:global

Svelte では :global という修飾子を使って、CSSをグローバルに記述することができます。あまり使いどころはないかもしれませんが、今回はせっかくなので使ってみました。 body を global にするのは普通にありそうです。

ブロック

Svelte にも if や each といった便利なブロックが存在します。例えば src/routes/Article.svelte はこんな感じになっています。

src/routes/Article.svelte
<script>
  export let articles;
</script>

<section class="article-container">
  <h2>Articles</h2>

  <div class="article-section">
    <h3>SRE</h3>
    {#each articles as article}
      {#if article.section === "sre"}
        <a href={article.url} class="article-link" target="_blank"
          >{article.title}</a
        >
      {/if}
    {/each}
  </div>
...

わかりやすいですね。

ルーティング

Svelte のルーティングは簡単ですが、やや特殊です。例えば、今回はスキルの詳細ページも必要だったため、そちらのルーティングを新規で作っています。

ルーティングを新しく作るときはルート名になるディレクトリを作成し、その下に同様に +page.svelte を配置すればOKです。 skills/[id] といった動的なルーティングも可能になります。

この場合は skills/[id]/+page.server.js にて、params からそれぞれのページで必要なデータを取得しています。

src/routes/skills/[id]/+page.server.js
import { loadSkills } from "$lib/server/skill";

async function getSkillFromJson(skillId) {
  const skills = await loadSkills();
  return skills.find((skill) => skillId === skill.id);
}

export async function load({ params }) {
  const skillId = params.id;
  const skill = await getSkillFromJson(skillId);
  return { skill };
}

export const prerender = true;

落穂拾い

export const prerender = true

export const prerender = true とすると、アプリケーションのビルド時にあらかじめ HTML をレンダリングしておく「プリレンダリング」という機能を有効化することができます。今回のポートフォリオサイトのような静的ページは有効にしておくと良いでしょう。

$: プレフィックス

Svelte には $: というプレフィックスが存在し、これはある変数が更新された時に、それに応じてDOM更新以外の別の処理も行いたい時に使用します。よくあるケースとしては、ある変数の変更に応じて別の変数も更新したい場合などです。便利ですね。

Conclusion

以上、SvelteKit でポートフォリオサイトを作ってみたメモでした。このポストが誰かの役に立てば幸いです。Svelte は楽しいし簡単なので、良かったら皆さんも何か作ってみてはいかがでしょうか。

Discussion