marko-run について
marko-run は Marko Run アプリを扱うエージェント向けの開発資料です。Vite のファイルベースルーティング、ストリーミング SSR、ハンドラー、検証、デプロイを解説します。
主な機能
- 01ファイルベースルーティングとレイアウト
- 02ストリーミング SSR とデータ読み込み
- 03型付きハンドラーと URL ヘルパー
- 04リクエスト検証とミドルウェア
- 05Vite 設定とデプロイアダプター
活用シーン
- 01ルートとネストしたレイアウトの実装
- 02検証付きリクエストハンドラーの追加
- 03データ読み込み中のストリーミング維持
- 04ビルドとデプロイの設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ svallory/ marko-ui --skill marko-runインストール後、Claude Code で/marko-runを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deploy.mdhandlers.mdrouting.md詳細
Marko Run
https://markojs.com/docs/marko-run から要点をまとめました。Vite 上のファイルベースルーティング、ストリーミング SSR、設定不要。references/routing.md(ファイル、パス、フラットルート、実行順序)、references/handlers.md(動詞ヘルパー、バリデーション、データ読み込み、コンテキスト、型付き URL、埋め込み)、references/deploy.md(Vite プラグイン、アダプター、CLI)。
基本的な考え方
src/routes/**—+で始まるファイルのみルーティング対象です。それ以外はすべて無視されます(コンポーネントやテストは自由に同じ場所に配置できます)。- リクエスト → ミドルウェア(ルートから末端)→ ハンドラー → レイアウト(ルートから末端)→ ページ。レイアウトとページは 1 つのコンポーネントにコンパイルされます。
ctx(ハンドラー/ミドルウェア)===$global(テンプレート)。ctx.paramsとctx.urlはデフォルトでブラウザーにシリアライズされます。それ以外はctx.serializedGlobalsで追加します。- データは
next({ ... })を通じてctx.data/$global.dataへ下方向に渡されます(浅いマージ)。プロミスを渡し、ページで<await>します —nextの前に await するとストリーミングが無効になります。 Runはグローバルです(インポート不要):Run.GET/HEAD/POST/PUT/DELETE/PATCH/OPTIONS/QUERY/ALL、Run.href、型Run.Context、Run.Platform。
クイックリファレンス
ファイル(routes 以下の任意の階層)
| ファイル | 役割 |
|---|---|
+page.marko | GET → HTML。パスごとに 1 つ。 |
+layout.marko | 入れ子になったレイアウト/ページをラップします。input.content = 子要素。$global = ctx。 |
+handler.ts | export const GET/POST/... = Run.X(...)。パスごとに 1 つ。Response を返すか、undefined を返すと next() が自動的に呼び出されます。HEAD がない場合は、GET から本文を除いたものになります。 |
+middleware.ts | export default Run.ALL(...)(すべての動詞)または Run.POST(...)(1 つの動詞)。ハンドラーの前に実行されます。オプションのみの形式 Run.ALL({ search }) は、以下のバリデーションを適用します。 |
+meta.json / +meta.ts | 静的な ctx.meta。動詞キー("POST": {...})はメソッドごとに浅く上書きします。 |
+404.marko、+500.marko | ルートのみ。Accept に text/html が含まれる場合に使用されます。ルートレイアウトでラップされます。 |
パスセグメント
users は静的 · _admin はパスなし(URL では無視され、レイアウト/ミドルウェアの適用範囲を定めます) · $id はパラメーター($ のみの場合はマッチしますが、キャプチャしません) · $$rest は末尾までのキャッチオール($$ はキャプチャせず、その内側には何もネストできません)。
フラットルート:projects.$projectId.members+page.marko。ディレクトリでも使用できます(projects.$projectId/members+page.marko)。ネストされたディレクトリとマージされます。複数パスは members,people+page.marko、グループは (members,people)、オプションは (home,) または (home,_x)。制御文字はバッククォートでエスケープします:`sitemap.xml`/+handler.ts。
ハンドラーとバリデーション
export const POST = Run.POST({ json: NoteSchema }, async (ctx, next) => {
const [note, issues] = await ctx.body; // Standard Schema → [value, issues]; fn validator → its return
if (issues) return Response.json({ issues }, { status: 422 });
return ctx.redirect("/thanks"); // PRG on success; next({ issues }) to re-render form with errors
});
- オプション:
params、search(関数または同期 Standard Schema。遅延実行され、初回アクセス時にキャッシュされます)、json(validator、maxBytes1MiB)、form(validator、maxBytes、maxParts1000、maxFiles20、maxFileBytes1MiB、onFile(ctx, file)。繰り返しフィールドは配列になります)。フォームではRun.POST(options, [auth, handler])のように配列で合成できます。 ctx.bodyはプロミスです(await ctx.body)。遅延実行され、json/formオプションがない場合はundefinedになります。ctx.request.json()は呼び出さないでください — バリデーションと制限を迂回します。- オプションはミドルウェアとハンドラー間でマージされます(最後のバリデーターが優先され、オブジェクトは浅くマージされます)。
ctxのメソッド:fetch(url, init)(内部で再リクエストし、parentを設定します)、render(template, input, init)、redirect(to, status=302)、back(fallback="/", status)。Run.href("/projects/$projectId/members", { params: { projectId: 42 }, search: {...}, hash })—.marko-run/routes.d.tsが tsconfig のincludeに含まれている場合、実際のルートに基づいて型付けされます。クライアントではコンパイル時に書き換えられます。
型
生成される .marko-run/routes.d.ts(ルートに tsconfig が必要です。include に追加してください)により、ファイルごとに Run.Context が絞り込まれます(params、meta、search、body、上流の data)。グローバルに拡張するには:declare module "@marko/run" { interface Context { db: DB } interface Platform { ... } } — アプリ全体で使うプロパティ用に限定してください。ルートごとのデータは next() で渡します。
設定/デプロイ
vite.configは不要です。必要な場合は、import marko from "@marko/run/vite"; plugins: [marko({ adapter, routesDir, trailingSlashes })]を指定します(すべての@marko/viteオプションも使用できます)。trailingSlashes:RedirectWithout(デフォルト)|RedirectWith|RewriteWithout|RewriteWith|Ignore。- アダプターの解決順序:オプション →
@marko/run-adapter*/*marko-run-adapter*という名前の最初の依存関係 → 組み込みの Node。パッケージ:@marko/run-adapter-node(Express ミドルウェア:routerMiddleware()、matchMiddleware()→req.route.invoke)、@marko/run-adapter-static(urls: [...] | async fn、リンクをクロール)、@marko/run-adapter-netlify({ edge: true }とnetlify.tomlのfunctions/edge_functions = "dist"、dist/publicを公開)。 - 埋め込み:
import * as router from "@marko/run/router"→router.fetch(request, platform)、router.match(method, path)とrouter.invoke(route, request, platform)。 - CLI(
marko-run、bun run/bunxで実行):dev(デフォルト、-p)、build(-o)、preview(-o -p -f)。すべて-c config、-e dotenvを指定できます。
よくある間違い
next()の前にGETでデータを await する → レスポンス全体がブロックされます。プロミスを渡してください。- 成功した
POSTからnext()を返す → 更新時に再送信され、URL を共有できません。代わりにリダイレクトしてください。 - 非同期の Standard Schema バリデーターは実行時にエラーになります。同期処理のみ使用してください。
ctx.body()— これは関数ではなく、プロミスです。- 常に独自の
Responseを返すハンドラーではページがレンダリングされません(そのハンドラーの HTTP メソッドはページの型付きコンテキストから除外されます)。 $$catch-allディレクトリ内にルートファイルをネストする — 探索されません。- クライアントで
$global.sessionを使えると思い込む — デフォルトでシリアライズされるのはparams/urlのみです。ctx.serializedGlobalsに追加してください(シークレットは絶対に追加しないでください)。