shinyaz.com

Hello World - shinyaz.com へようこそ

目次

はじめに

shinyaz.com へようこそ!このブログは Next.js 16 と MDX で構築されたミニマル・モノトーンの個人技術ブログです。

この記事では、ブログを支える技術とその特徴を詳しく紹介します。

技術スタック

カテゴリ技術
フレームワークNext.js 16 (App Router)
スタイリングTailwind CSS v4
コンテンツ管理MDX + Velite
コードハイライトShiki + rehype-pretty-code
数式レンダリングremark-math + rehype-katex
ダークモードnext-themes
PWASerwist
テストVitest + Playwright
デプロイVercel

バイリンガル対応 (日本語 / English)

本ブログはすべてのページが日本語と英語の両方で提供されています。外部 i18n ライブラリを使わず、TypeScript の辞書オブジェクトだけで UI 翻訳を実現しています。

const dictionaries = {
  ja: {
    site: { name: "shinyaz.com", description: "..." },
    nav: { home: "ホーム", blog: "ブログ", ... },
    // ...
  },
  en: {
    site: { name: "shinyaz.com", description: "..." },
    nav: { home: "Home", blog: "Blog", ... },
    // ...
  },
};
  • ヘッダーの「EN / JP」ボタンで、同じページに留まったまま言語を切り替え可能
  • URL は常にロケールプレフィックス付き(例: /ja/blog/.../en/blog/...
  • ブラウザの Accept-Language ヘッダーから自動でリダイレクト
  • コンテンツファイルも言語ごとにディレクトリ分割(content/posts/ja/content/posts/en/

ダークモード

next-themes によるクラスベースのダークモード切替に対応しています。テーマトークンは CSS カスタムプロパティで定義され、:root(ライト)と .dark(ダーク)で切り替わります。

コードブロックも Shiki のデュアルテーマ(github-light / github-dark)を使い、テーマに連動して自動で切り替わります。

example.ts
function greet(name: string): string {
  return `Hello, ${name}! Welcome to shinyaz.com.`;
}
 
console.log(greet("World"));

コンテンツパイプライン

記事は MDX ファイルで執筆し、Velite がビルド時に静的データへ変換します。

content/posts/{en,ja}/*.mdx
  → Velite (prebuild)
    → .velite/ (静的データ)
      → Next.js (App Router)

MDX は以下のプラグインチェーンで処理されます:

  1. rehype-slug — 見出しに自動で ID を付与(アンカーリンク用)
  2. remark-math + rehype-katex — 数式レンダリング
  3. rehype-pretty-code (Shiki) — シンタックスハイライト(デュアルテーマ)

コードハイライト

複数の言語に対応したシンタックスハイライトです。ファイル名の表示も可能です。

fibonacci.py
def fibonacci(n: int) -> list[int]:
    """Generate Fibonacci sequence up to n terms."""
    if n <= 0:
        return []
    fib = [0, 1]
    for _ in range(2, n):
        fib.append(fib[-1] + fib[-2])
    return fib[:n]
 
print(fibonacci(10))

インラインコードも使えます: const x: number = 42;

数式レンダリング

LaTeX 記法による数式のレンダリングに対応しています。

インライン数式: E=mc2E = mc^2

ブロック数式:

ex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}

二次方程式の解の公式:

x=b±b24ac2ax = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}

目次の自動生成

各記事には見出し(h2, h3)から自動生成される目次が表示されます。IntersectionObserver によるスクロールスパイで、現在読んでいるセクションがハイライトされます。

クライアントサイド検索

外部サービスを使わない、完全クライアントサイドの全文検索を搭載しています。

  • タイトル・説明・カテゴリ・タグの4フィールドを横断検索
  • スペース区切りの AND 検索(すべてのトークンにマッチする記事のみ表示)
  • リアルタイム絞り込み(タイプするたびに結果が更新)
  • URL クエリパラメータと同期(?q=nextjs で直接リンク可能)

フォント

IBM Plex ファミリーを採用しています。

  • IBM Plex Sans — 欧文テキスト用(400, 500, 600, 700)
  • IBM Plex Mono — コードブロック用(400, 700)
  • IBM Plex Sans JP — 日本語テキスト用(400, 500, 700、遅延読み込み)

日本語フォントはファイルサイズが大きいため preload: false で遅延読み込みし、初期表示パフォーマンスを最適化しています。

SEO

検索エンジン最適化のため、以下を実装しています:

  • 構造化データ (JSON-LD): WebSite スキーマ(レイアウト)、BlogPosting + BreadcrumbList スキーマ(各記事)
  • Open Graph / Twitter Card: ソーシャルシェア用のメタタグ
  • hreflang / canonical: 言語間の対訳ペアを正しくマークアップ
  • サイトマップ: 全ロケールの URL を含む動的サイトマップ
  • RSS / Atom フィード: ロケールごとに RSS 2.0 と Atom 1.0 を自動生成(最新20件)

PWA & オフライン対応

Serwist による Service Worker で PWA に対応しています。

  • ビルド時に precache manifest を自動注入し、静的アセットをプリキャッシュ
  • ネットワーク未接続時はオフラインフォールバックページを表示
  • ランタイムキャッシュ戦略で Next.js のページとアセットを最適にキャッシュ

セキュリティ

すべてのルートに HTTP セキュリティヘッダーを適用しています:

  • Content-Security-Policy — リソースの読み込み元を制限
  • Strict-Transport-Security — HTTPS を強制(max-age 2年)
  • X-Content-Type-Options / X-Frame-Options — MIME スニッフィング・クリックジャッキング防止
  • Permissions-Policy — カメラ・マイク・位置情報など不要な API を無効化

テスト

品質を担保するため、二層のテスト戦略を採用しています。

  • ユニット / コンポーネントテスト (Vitest) — ライブラリ関数とコンポーネントの振る舞いを検証
  • E2E テスト (Playwright) — ナビゲーション、ブログ、検索、i18n、オフライン、セキュリティヘッダーを統合検証

まとめ

shinyaz.com は、ミニマルなデザインの裏側で多くの技術的工夫を取り入れています。今後もこのブログで技術に関する記事を投稿していきます。気になる機能があれば、ぜひソースコードも覗いてみてください。

共有する