---
title: "layout: html"
date: 2026-08-12T00:00:00.000Z
updated: 2026-08-12T00:00:00.000Z
tags:
  - garden
url: https://baku89.com/garden/layout-html
---

# layout: html

本文をそのまま単体の HTML 文書として出力するレイアウト。ヘッダーもフッターも\
AppDock もなく、サイトの CSS もフォントも Nuxt のランタイムも一切載らない。\
書いた HTML がそのままブラウザに届く。

```
---
content_id: 01K...
layout: html
visibility: public
---

<style>
  body { margin: 0; background: #000 }
</style>

<canvas id="c" width="800" height="800"></canvas>

<script>
  const c = document.querySelector('#c')
  // …
</script>
```

## 何をしているか

`server/middleware/html-layout.ts` が Nuxt のレンダラより手前で応答する。\
`page.body`（＝ブラウザが描画するのと同じ AST）を `utils/markdown/renderPageHtml.ts`\
が HTML 文字列に直列化し、最小限の骨格を被せて返す。アプリのバンドルも\
hydration も発生しない。

```
GET /sketch  →  <!doctype html>
                <html lang="ja">
                <head>
                <meta charset="utf-8">
                <meta name="viewport" content="…">
                <title>Sketch</title>
                <style>…</style>          ← 本文の <style> がここへ上がる
                </head>
                <body>
                <canvas id="c"></canvas>
                <script>…</script>        ← <script> は本文中の位置のまま
                </body>
                </html>
```

`<!doctype html><html><head>…` まで含めたフル HTML を書いてもよい。Markdown\
パーサは生 HTML をフラグメントとして読むので `<html>` / `<head>` / `<body>` の\
ラッパは落ち、中身だけがトップレベルに並ぶ。`renderPageHtml` はそのうち\
`<title>` `<meta>` `<link>` `<base>` `<style>` を `<head>` に引き上げ直す。\
`<script>` だけは引き上げない — 参照する要素より先に走ってしまうため、本文中の\
位置を保つ。

`charset` / `viewport` / `<title>` は既定で補われるが、本文が自分で書いていれば\
そちらが優先される。`draft: false` かつ `visibility: public` でないページには\
`noindex, nofollow` の robots meta が付く（通常ページの `pages/singular.vue` と\
同じ規則）。

## Markdown もそのまま使える

パイプラインは通常ページとまったく同じなので、`# 見出し` も `**強調**` も\
Wikilink も効く。「HTML しか書けない」のではなく「HTML が素通しで通る」レイアウト。

## 制約

- **インラインイベントハンドラは消える。** `onclick=` などの `on*` 属性は MDC の\
  パーサ（`validateProps`）が安全のために剥がす。`<script>` の中で\
  `addEventListener` を使う。
- **ブロック要素でないタグは `<p>` に包まれる。** Markdown の HTML ブロック判定は\
  CommonMark のタグ一覧に従うので、`<canvas>` や `<custom-element>` を単独行に\
  書くと `<p><canvas …></canvas></p>` になる。リセット CSS が無い分、`<p>` の\
  デフォルトマージンがそのまま効く。`<div>` で包むか、CSS 側で `p { margin: 0 }`\
  を書く。
- **MDC コンポーネントは描画されない。** `::hero` や `:date[…]` は Vue\
  コンポーネントであり、ここには Vue がいない。`<hero>` のような不活性なカスタム\
  要素として出力される。
- **公開ページ専用の経路。** バイパスが効くのは `visibility` が `public` /\
  `unlisted` のページだけ。`private` / `protected` はアクセス判定が\
  アプリ側にあるので通常の SSR に落ち、記事として表示される。

## 編集する

素の HTML 文書には AppDock もコマンドパレットもないので、`?edit` を付けると\
同じ URL で通常のアプリに戻れる。

```
/sketch        → 素の HTML
/sketch?edit   → いつもの Nuxt アプリ（エディタが開ける）
```

サイト内リンクから `layout: html` のページへ SPA 遷移した場合は、\
`pages/singular.vue` がブラウザに URL を投げ直して本物のドキュメントリクエストに\
切り替える。この再読み込みは「1 タブ・1 パスにつき 1 回」に制限してある\
（`utils/pages/bareHtmlLayout.ts` のコメント参照）— まだレプリケーションされて\
いないページなど、サーバが応答できないケースでループしないため。
