---
title: コース
order: 12.6
icon: phosphor-duotone:graduation-cap
tags: [ガイド, コース]
---

# コース

**コース** は、一連のページを案内付きの番号順シーケンス - レッスン1、レッスン2、
レッスン3…… - に変えます。自動生成される独自の番号付きインデックス、その
コース自身に閉じた「Lesson N of M」の前後リンク（サイト全体のページ間の順序
とは独立しています）、そして読者が一度レッスンを開くと、そこから先は自分の
ブラウザの中で進捗が記録されます: どのレッスンを完了したか、そして最後に
訪れたレッスンへ戻る「Continue where you left off」リンクです。

## マニフェスト

`docs/data/courses.yaml`（`.yml`/`.json` も使えます - [データファイル](data-files.md)
を参照）ファイルを追加します。各トップレベルキーが1つのコースで、その
`lessons` 配列がそのコース自身のページを順番に並べます - 配列内の位置が
そのままレッスン番号 *になります*:

```yaml title="docs/data/courses.yaml"
getting-started:
  title: "Getting Started with BoxLang"
  description: "A guided walkthrough from install to your first deployed site."
  lessons:
    - guides/course/introduction.md
    - guides/course/windows-installation.md
    - guides/course/mac-installation.md
    - guides/course/creating-a-new-site.md
```

`lessons` の各エントリは `docs/` 相対のパス文字列で、`nav.json` 自身が使って
いるのと同じ相対パスの規約です。レッスン自身のタイトル/サマリは *そのページ
自身のフロントマター* から取得され - マニフェストに複製されることはありません
- そのため、ページのタイトルをリネームしたり、サマリを編集したりすると、
コースインデックスに自動的に反映されます。複数のコースは、単に同じファイル
内に複数のトップレベルキーがあるだけのことです。

## インデックス

Markdown のどこかに1行を置くだけで、そのコース自身の番号付きインデックスが
レンダリングされます:

```markdown
::: course id="getting-started" :::
```

これは本物のセマンティックな `<ol>` を1つレンダリングします - レッスンごとに
1つの番号付きリンクがあり、それぞれがそのレッスン自身のタイトルとサマリを
持ちます - さらに（最初は非表示で、読者が実際にコースを開始してから
クライアントサイドで埋められる）進捗バーと「Continue where you left off」
リンクが付きます。`id` のタイプミスや、レッスンがすべて揃っていないコースは、
ビルドを失敗させる代わりに、小さな目に見える注記に成り下がります。

## レッスンページ

コースの `lessons` に列挙されたページはすべて、自動的に `course` コンテキスト
（`page.course` - [コンテキスト変数](variables-and-functions.md#context-variables)
を参照）を、自分自身の位置・タイトル、そして *そのコースに閉じた* 前後リンク
とともに受け取ります - `page.course.prevLesson`/`.nextLesson` は、常にその1つの
コース内だけを移動します。これは、コースの有無にかかわらずナビツリー全体を
歩く、サイト自身のグローバルな
[`page.prevPage`/`.nextPage`](variables-and-functions.md#context-variables) とは
異なります。レッスンは、自分がどのコースに属しているか、どこに属しているかを
申告する必要はありません - それを決めるのはマニフェストただ1か所であり、
あるページが誤って2つのコースに入ってしまうこともありません（それは執筆上の
ミスであり、ビルドが明確なエラーで検出します - [エラー](#errors) を参照）。

現時点では、bootstrap テーマだけが、この閉じたナビゲーション - 「Lesson N of
M」バッジ、コースに閉じた前後ページャー、「Mark complete」トグル - を
レッスンページ上に直接レンダリングします。他の組み込みテーマもすべて
`page.course` 自体は正しく計算します（そのため、それらのテーマを使っている
プロジェクトでも、独自の[テーマオーバーライド](themes.md#overriding-a-theme)
経由ですでにこれを表示できます）。残りの組み込みテーマにおけるネイティブな
表示はロードマップ上にあります。

## 進捗トラッキング

読者が一度レッスンを開くと、そのページ自身の隠しマーカーが
`course-progress.js`（すべての組み込みテーマで共有され、常に含まれています）
に訪問を記録するよう伝えます - 設定もオプトインも不要です。進捗は、その
ブラウザ自身の `localStorage` の中、`bxsites-course-progress-<courseId>`
というキーの下に完全に保存されます:

```json
{
  "firstStarted": "2026-08-30T14:02:11.000Z",
  "lastVisited": { "url": "/guides/course/mac-installation/", "at": "2026-08-30T14:22:03.000Z" },
  "completed": {
    "/guides/course/introduction/": "2026-08-30T14:05:00.000Z",
    "/guides/course/windows-installation/": "2026-08-30T14:12:44.000Z"
  }
}
```

レッスンは、そのページが訪問された瞬間に自動的に完了としてマークされます。
「Mark complete」/「Mark incomplete」トグルを使えば、読者は誤って自動マーク
されたものを取り消したり、後から戻ってレッスンを再マークしたりできます。
コースインデックスは、このデータをそのまま読み取って、チェックマーク、
進捗バー（「N of M complete」）、そして続きから再開するリンクを埋めます。

これは純粋なクライアントサイドの拡張であり、それなしでもすでに完全に機能
しているコースの上に重ねられているだけです - 番号付きインデックスも、
コースに閉じた前後ページャーも、どちらもサーバーサイドでレンダリングされる
ため、JavaScript を無効にした読者や検索クローラーにも、基本機能一式 -
正しく番号が振られ、正しくリンクされたすべてのレッスン - がそのまま見えます。
進捗トラッキングは、コースが機能するために必須のものは何もありません。
ストレージがそもそも利用できない場合（プライベートブラウジング、サイト
データのブロックなど）は、静かに「進捗は記憶されない」という状態に縮退する
だけで、エラーにはなりません。

進捗はブラウザ単位のもので、背後にアカウントやバックエンドは一切ありません
- デバイス間で同期されることはなく、誰が何を読んだかというサーバーサイドの
記録もありません。それがプロジェクトにとって本当に必要な要件であれば、それは
今日のこの機能が対応する範囲の外です。

## 今後の拡張

この機能が意図的に**まだ**作り込んでいないものが2つありますが、既存の
`courses.yaml` を壊すことなく、将来そこへ育っていけるように形作られています:

- **レッスン間のクイズ。** 解決済みのレッスンは、内部的にはすでに `type`
  （現時点では常に `"lesson"`）を持っています - 将来のバージョンでは、
  `lessons` のエントリとして、裸のパス文字列に加えて、小さなオブジェクト
  （例: `{ path: ..., type: "quiz" }`）を受け付けられるようになる可能性が
  あります。裸のパスだけを列挙しているコースには何の変更も必要ありません。
- **コース末尾の最終テスト/評価。** マニフェストのスキーマは、コースごとの
  任意のトップレベルキー `finalTest` を予約しています（現時点では無視され
  ます）- これは、後からスキーマの破壊的変更なしにこの機能を追加できる
  ようにするためだけのものです。自分のマニフェストの中で、このキーを他の
  用途に使わないでください。

## なぜフロントマターではなくマニフェストなのか？

コースのレッスンは、`courses.yaml` の中でただ一度だけ宣言されます - 各
レッスン自身のフロントマターに散らばる `course: getting-started` のような
フィールドとしてではありません。ページ単位のフィールドは、マニフェストと
並ぶ、強制力のない第二の信頼できる情報源になってしまいます。そして両者が
食い違うこと（あるレッスンのフロントマターは1つのコースを指しているのに、
マニフェストでは別のコースに列挙されている - あるいはどこにも列挙されて
いない）は、まさにこの設計が避けようとしている、静かなズレそのものです。
マニフェストこそが、コースの形 - どのレッスンを、どの順序で - を決める
唯一の場所です。レッスンページ自身が、自分がどのコースに属しているか、
どこに属しているかを知る必要は決してありません。

## スコープ

- `lessons` が現在ビルド中のツリーの中で実在するページとして*すべて*揃って
  いないコースは、そのツリーについては静かにスキップされます。ビルド失敗
  にはなりません - これが重要なのは、`docs/data/courses.yaml` がプロジェクト
  全体で一度だけ読み込まれ（[データファイル](data-files.md#scope) がすでに
  持っているのと同じスコープです）、すべてのバージョン/ロケールツリーで
  変更なく再利用されるためです。裸の `docs/versions/<name>/` スナップショット
  には、コースのレッスンファイルがまったく含まれていないこともあります。
- レッスンは1つのコースにしか属せません - 同じパスを2つの異なるコースの下に
  列挙するのは実際の執筆上のミスであり、例外が送出されます（[エラー](#errors)
  を参照）。
- この最初のバージョンでは、ネスト/マルチトラックのコースはなく、バージョン
  ごとに異なるレッスン順を持つバージョン別のコースマニフェストもありません。

## エラー

- `BxSites.InvalidConfig` - `docs/data/courses.yaml` の形に問題があります:
  コースの値がオブジェクトでない、`title` が欠けている、`lessons` がパス
  文字列の空でない配列になっていない、または同じレッスンパスが複数の
  コースの下に列挙されている、のいずれかです。
