インク・コード
随筆

このサイトについて

「インク・コード」では、Web 開発における学習・実践・考察と、関連技術の探究や検証を記録しています。

789 字4 分読み込み中
このサイトについて

「インク・コード」は、Web 開発における学習・実践・考察を中心に記録する個人技術ブログです。 入門から実践までの学習ノートだけでなく、開発中に遭遇した問題や、その解決方法についてもまとめています。

私にとって、文章を書くことも学習の一部です。学んだ内容を改めて整理し、自分の言葉で表現し直すことで、見落としていた点や理解が曖昧だった部分に気づくことがあります。また、断片的だった知識同士のつながりも少しずつ見えてきます。同時に、ブログを自分で構築・運用すること自体が、Web 技術を実践する機会にもなっています。

このサイトは当初、QuietPages の設計思想を参考にしました。その後、必要なものが変わるにつれて、サイトのデザインや機能も継続的に調整しています。このブログは単に学習内容を記録する場所ではなく、それ自体が Web 開発を学び、実践する取り組みの一部になっています。

ページ概要

サイトはコンテンツの閲覧と情報表示を中心に構成されており、記事閲覧、アーカイブ、データ統計、お問い合わせ、著作権情報などのページを用意しています。

コンテンツ閲覧

ホーム では、主に最新コンテンツと注目記事を表示し、最近の更新をすぐに確認できるようにしています。Hero エリアでは背景画像を切り替えることができ、画像ソースとして Unsplash または Lorem Picsum を選択できます。

記事一覧 では記事の閲覧と検索ができ、カテゴリー、タグ、コレクションによる絞り込みに加えて、並び替えとページネーションにも対応しています。記事詳細ページでは、前後の記事や関連記事から続けて読むことができ、Giscus のコメント欄からディスカッションに参加することもできます。

アーカイブ では、すべての記事を時系列で整理し、年・月・リストの3種類のビューを提供しています。年ビューでは年ごとの記事分布と更新頻度、月ビューではカレンダー上で各日の公開状況、リストビューでは公開日時ごとにグループ化された記事を確認でき、時系列での閲覧や振り返りに適しています。

サイト情報

データ統計 では、記事数や文字数、公開頻度、カテゴリー構成のほか、アクセス傾向や訪問者の地域分布などをまとめています。ページ内のグラフは ECharts を利用し、地域分布は地図で表示します。AMap のセキュリティキーを設定している場合は AMap JS API 2.0 を使用し、未設定の場合は Leaflet で CARTO の地図タイルを読み込みます。

お問い合わせ では、サイト内からメールを送信できます。送信前にメール内容をプレビューでき、実際の送信には Resend を使用しています。

著作権表示 では、サイト内で使用しているフォント、画像、テクスチャ、サードパーティーリソースの出典とライセンス情報を記録しています。

外部リソース

ブックマーク では、よく使う Web サイトをカテゴリーごとに整理し、検索と絞り込みに対応しています。

フレンドリンク では、継続的にチェックしている個人サイトを紹介するとともに、リンク交換の申請窓口も用意しています。

ビジュアルと閲覧体験

サイトの見た目は複数の設定によって制御されています。スキンでテーマカラーを切り替え、ライト/ダークモードで全体の明るさを調整し、テクスチャによってページ背景に細かな表情を加えています。また、見出しと本文ではそれぞれ別のフォントを選択でき、フォントリソースには ZeoSeven Fonts を使用しています。本文の文字サイズも、読みやすさの好みに合わせて調整できます。

Rough Notation を使って、見出し、ボタン、重要な内容に手書き風の注釈を付けています。ページレイアウトが変化した場合は、注釈の位置も再計算されます。

サイト内には、ページ操作に合わせた少量の UI 効果音も追加しています。できるだけ操作と同期するようにしていますが、音声の読み込み、ブラウザーのスケジューリング、OS の再生経路などの影響により、初回再生時や長時間再生されていなかった後の再生では、比較的大きな遅延が発生する場合があります。

サイト内ナビゲーションには Astro ClientRouter を使用してクライアントサイドルーティングを実現し、View Transition をベースにページ遷移アニメーションをカスタマイズすることで、ページ移動時の視覚的な断絶感を抑えています。

ページのスムーズスクロールには Lenis を使用しています。一方、目次、コメント欄、エディター、設定パネルなど、独立したスクロール領域を持つコンポーネントではネイティブスクロールを維持し、スクロール動作同士が干渉しないようにしています。

コード実験

記事内のコードは、インラインコード、複数行コード、実行可能なコードの3種類に分けられます。

インラインコードは rehype-pretty-code で処理します。このプラグインはコードに付けられた言語指定を読み取り、Shiki で構文解析を行い、最終的に token スタイル付きの HTML を生成します。

言語指定はインラインコードの末尾に記述します。たとえば `const count = 1{:javascript}` は const count = 1 としてレンダリングされます。言語を指定した場合は対応する構文でハイライトされ、指定していないインラインコードには基本スタイルが適用されます。

複数行のコードを表示する場合は、```javascript のように言語指定付きの Markdown コードブロックを使用します。インラインコードと同じハイライトエンジンを使用しますが、処理の入口は異なり、rehype-pretty-code のインラインノード変換は通りません。

const count = 1;
console.log(count);

記事では MDX を使って実行可能なコード実験コンポーネントを埋め込むこともでき、読者はコードを直接編集してリアルタイムに実行できます。これらのコンポーネントは記事ページ側で一括して注入されるため、MDX 内で使用するときに改めて import する必要はありません。

フロントエンド Playground

フロントエンド Playground は Sandpack をベースに実装しており、主に HTML、CSS、JavaScript、フロントエンドコンポーネントなど、ブラウザー上で動作するコードの実行・デモに使用します。複数ファイルの編集に対応し、リアルタイムプレビュー、コンソール出力、コード整形、レイアウト調整などの機能を備えています。各サンプルでは、最初に開くファイルや、初期表示をプレビュー/コンソールのどちらにするかも指定できます。

<Playground
  codeId="about/frontend-playground"
  title="フロントエンド・ライブプレビュー"
  activeFile="/App.js"
  initialPane="result"
/>

以下は編集・実行可能なサンプルです。左側のファイルを変更すると、右側のプレビュー画面またはコンソールで実行結果を確認できます。

フロントエンド・ライブプレビュー准备中正在读取代码文件,请稍候…

リモートコードランナー

リモートコードランナーは CodeMirror でソースコードを編集し、実行リクエストを訪問者が選択したリモート実行プラットフォーム(Judge0 や Wandbox など)へ転送します。Python、Java、C++ など、インタープリターやコンパイラーを必要とするコードの実行に適しています。

<RemoteCodeRunner
  codeId="about/python-remote-execution"
  title="Python リモート実行"
  language="python"
  stdin="インク・コード"
/>

以下は編集・実行可能なサンプルです。標準入力を設定してコードを実行すると、出力パネルで結果を確認できます。

远程代码执行

Python リモート実行

布局
main.py40 字符

选择平台并点击“运行”,结果会显示在这里。

Warning

リモート実行サービスの悪用を防ぐため、デプロイ時には各プラットフォームの API Key、Token などの認証情報を適切に保管し、ブラウザー側へ公開したり、公開リポジトリへコミットしたりしないでください。

実行プラットフォームごとに呼び出しクォータやサービス上の制限があります。上限に達するとコードが正常に実行できなくなる場合があるため、その場合は別の実行プラットフォームに切り替えて再度試してください。

ブラウザー側 SQL Playground

ブラウザー側 SQL Playground は、sql.js と WebAssembly を利用して SQLite をブラウザー内で実行し、リモートデータベースへの接続を必要としません。連続した CRUD 操作に対応し、必要に応じて現在選択している SQL の一部分だけを実行することもできます。

一般的な MySQL のサンプルを扱いやすくするため、SQLite で実行する前に軽量な互換処理を追加し、一部の MySQL 構文を SQLite で実行可能な形式へ変換しています。互換レイヤーで処理できない文はそのまま SQLite に渡され、SQLite が対応していない構文を含む場合は対応するエラーが返されます。

複数のコンポーネントで同じ databaseId を使用すると、同一のブラウザー側データベースを共有できます。データベースのスナップショットは現在のブラウザーの IndexedDB に保存されるため、異なる記事やセクション間でも継続して実験できます。

databaseId を設定していない場合、各コンポーネントはそれぞれ独立した一時データベースを使用します。状態を保持する必要がない、互いに独立した SQL サンプルの実行に適しています。

<SqlPlayground
  codeId="about/sqlite-local-execution"
  title="SQLite ローカル実行"
/>

以下は編集・実行可能なサンプルです。複数の文をまとめて実行することも、選択した SQL 文だけを実行することもできます。

浏览器端 SQL 执行

SQLite ローカル実行

布局
query.sql195 字符
执行结果

プロジェクト構成

以下では、プロジェクトの主なディレクトリと重要なファイルを挙げ、それぞれの用途を簡単に説明します。

プロジェクト構成
  • srcサイトの主要なソースコードディレクトリ。
    • componentsサイト全体で使用する各種機能コンポーネント。
    • contentAstro Content Collections で使用する記事と構造化データ。
  • publicビルド処理を必要とせず、そのまま公開できる静的リソース。
  • migrationsCloudflare D1 データベースのマイグレーションファイル。
  • scripts記事作成とローカル開発に使用する補助スクリプト。

サイト設定

サイト情報

サイトの基本設定は src/configs/site.ts にまとめています。

const siteConfig = {
  name: "インク・コード",
  description: "Astro で構築した、技術の探究と日々の開発を記録する個人技術ブログ",
  url: "https://example.com",
  author: {
    email: "you@example.com",
    location: "あなたの都市",
    coordinates: [30.63, 104.0], // [緯度, 経度]
  },
  clientRouter: true,
  codeTheme: {
    light: "catppuccin-latte",
    dark: "one-dark-pro",
  },
  giscus: {},
  nav: [],
  footerNav: [],
};

国際化

国際化の設定は src/configs/i18n.ts にあります。

const i18nConfig = {
  enabled: true,
  defaultLocale: "zh-CN",
  locales: ["zh-CN", "en", "ja"],
  fallback: {
    en: "zh-CN",
    ja: "zh-CN",
  },
  routing: {
    prefixDefaultLocale: false,
  },
};
  • enabled:国際化を有効にするかどうかを決めます。無効にすると、サイト全体で defaultLocale のみを使用します。
  • defaultLocale:サイトのデフォルト言語と、言語プレフィックスのない URL で使用する言語を決めます。
  • locales:サイトが対応する言語を決めます。言語コードは URL のプレフィックスとして使用されます。新しい言語を追加する場合は、src/types/i18n.ts の SUPPORTED_LOCALES と文言辞書もあわせて拡張する必要があります。
  • fallback:ある言語の記事翻訳が存在しない場合に、どの言語の URL へフォールバックするかを決めます。たとえば { en: "zh-CN" } は、英語版が存在しない場合に対応する中国語ページへ移動することを意味します。フォールバックのマッピングがない場合は、そのまま 404 を返します。
  • prefixDefaultLocale:デフォルト言語で /<locale>/ プレフィックスを使用するかどうかを制御します。

地図サービス

地図関連の設定は src/configs/map.ts にまとめています。ローカル開発では、ブラウザーが必要とする公開設定をプロジェクトルートの .env に置き、Worker のリクエスト実行時に使用するセキュリティキーは .dev.vars に置きます。2つのファイルは役割が異なるため、同じ変数を重複して記述しないでください。Cloudflare 本番環境での設定場所については、後述の Cloudflare へのデプロイ を参照してください。

.env:ビルド時およびブラウザーで必要となる公開設定。

PUBLIC_AMAP_KEY=あなたの AMap Web API Key
PUBLIC_CARTO_KEY=あなたの CARTO Basemap Key

.dev.vars:ローカル Worker 実行時に使用する Secret。

AMAP_SECURITY_CODE=あなたの AMap セキュリティキー

PUBLIC_AMAP_KEY は AMap JS API 2.0 の Web 用 Key であり、ブラウザーからのリクエストに含める必要があります。AMAP_SECURITY_CODE は AMap のセキュリティキーで、ローカル環境・本番環境ともに同一オリジンの /_AMapService プロキシがサーバー側で読み取るため、ブラウザーには公開されません。

PUBLIC_AMAP_KEY が設定されていない場合、コンポーネントは自動的に Leaflet と CARTO へフォールバックします。Key を設定済みでも AMAP_SECURITY_CODE が不足している場合は、AMap のプロキシリクエストが失敗するため、先にこの変数を設定してください。

PUBLIC_CARTO_KEY は CARTO のベースマップサービスで使用します。地図タイルのリクエストではブラウザーからこの値が送信されるため、これも公開 Key に該当します。地図にウォーターマークが表示されるのを避けるため、CARTO で Key を取得して設定することを推奨します。

Cloudflare へのデプロイ

このプロジェクトは @astrojs/cloudflare を利用して Cloudflare Workers 向けにビルドし、wrangler.jsonc で Worker、静的リソース、D1 データベースのバインディングを宣言しています。

本プロジェクトでは、Cloudflare Workers の GitHub 連携を使って自動デプロイしています。リポジトリを接続した後、指定したブランチへコードを push すると、Cloudflare が自動的にビルドとデプロイを実行します。詳しい設定については Workers Builds ドキュメント を参照してください。

初回設定

  1. 自分用の D1 データベース を作成します。

    pnpm exec wrangler login
    pnpm exec wrangler d1 create ink-code-analytics

    コマンドで返された database_id とデータベース名を wrangler.jsonc に記述し、 ink_code_analytics という binding はそのまま残してください。

  2. Cloudflare ダッシュボードで GitHub リポジトリを接続します。

    • 新しい Worker:Workers & Pages → Create application → Import a repository
    • 既存の Worker:Worker の Settings → Builds → Connect
    • このリポジトリと本番ブランチを選択し、ルートディレクトリに / を指定
    • ビルドコマンドに pnpm run build、デプロイコマンドに npx wrangler deploy を指定
    • Cloudflare 側の Worker 名が ink-code で、wrangler.jsonc 内の name と一致していることを確認
  3. 環境変数を設定します。

    環境変数は「ビルド時」と「Worker 実行時」の2種類に分かれます。読み込まれるタイミングが異なるため、それぞれ別に設定する必要があります。

    • ビルド時変数:pnpm run build の実行時に Astro/Vite が import.meta.env から読み込みます。ローカルでは .env、Cloudflare では Worker → Settings → Builds → variables and secrets に設定します。ここには以下を設定します。
      • SITE_URL:本番サイトの URL
      • PUBLIC_AMAP_KEY、PUBLIC_CARTO_KEY、PUBLIC_AMAP_SERVICE_HOST、PUBLIC_HERO_IMAGE_PROVIDER:ブラウザーコードまたはリクエスト URL に含まれる公開設定。PUBLIC_* に Secret を保存してはいけません。
    • Worker 実行時変数:Worker がリクエストを受け取った後、cloudflare:workers の env から読み込みます。ローカルでは .dev.vars、Cloudflare では Worker → Settings → Runtime variables and Secrets に設定します。ここには以下を設定します。
      • RESEND_API_KEY、RESEND_FROM_EMAIL:お問い合わせ API がリクエスト実行時に読み取ります。API Key は Secret として保存します。
      • AMAP_SECURITY_CODE:AMap のセキュリティキー。/_AMapService プロキシが読み取り、上流リクエストへ追加します。
      • UNSPLASH_ACCESS_KEY:/api/hero-image/ が読み取り、ブラウザーには渡されません。ランダム画像 API では UNSPLASH_SECRET_KEY は不要です。
      • CODE_RUN_RATE_LIMIT_SALT:コードランナーのレート制限用ソルト。Secret として保存します。
      • コード実行プラットフォームの認証情報と実行時エンドポイント:完全な変数名はリポジトリルートの .dev.vars.example を参照してください。認証情報はすべて Secret として保存します。

    ビルド時変数は自動的に Worker の実行時 env になるわけではなく、実行時変数も Astro/Vite のビルドには使用されません。PUBLIC_* の値はブラウザーコードやリクエスト URL にコンパイルされるため、公開しても問題のない Key のみを使用してください。API Token、Client Secret、パスワード、ソルトには決して PUBLIC_ プレフィックスを付けないでください。

    ローカル開発では、2つのファイルを併用することを推奨します。.env にはビルド時設定と公開設定のみを置き、.dev.vars には Worker 実行時設定と Secret のみを置きます。Cloudflare のローカル環境変数ドキュメント の仕様では、.dev.vars が存在する場合、Wrangler は .env の値を Worker の env にマージしません。そのため、同じ変数を2つのファイルへ重複して記述してはいけません。.dev.vars を作成しない場合は、.env だけをローカルのビルド時・実行時設定の共通ソースとして使用することもできますが、その場合はすべての実行時変数を .env に置く必要があり、Secret を明確に分離したい場合には適していません。

  4. 初回デプロイ前に、マイグレーションをリモート D1 に適用します。GitHub による自動デプロイは Worker のビルドと公開のみを行い、データベースマイグレーションは自動では実行しません。

    pnpm exec wrangler d1 migrations apply ink-code-analytics --remote

    ローカル開発では、先に --local を使って確認できます。今後新しいマイグレーションを追加した場合も、SQL に問題がないことを確認してから再度 --remote を実行してください。 適用済みのマイグレーションは変更・削除せず、より大きい番号のものを追加してください。詳しいルールは D1 migrations ドキュメント を参照してください。

日常の公開

コードを変更した後は pnpm check を実行し、変更をコミットして Cloudflare が監視しているブランチへ push します。

git add .
git commit -m "サイトを更新"
git push origin main

Cloudflare が自動的にサイトをビルドしてデプロイします。ビルドに失敗した場合は、まず Worker の Deployments → View build history でログを確認してください。

リソースとライセンス

  • Web フォントは ZeoSeven Fonts を使用しています。
  • Hero 背景の画像ソースは PUBLIC_HERO_IMAGE_PROVIDER で選択でき、Unsplash または Lorem Picsum を使用できます。
  • 背景テクスチャは Transparent Textures を使用しています。

各リソースのライセンスと使用上の説明については、著作権ページ を参照してください。リソースの出典や利用方法に問題がある場合は、お問い合わせページ からお知らせください。