KDP(電子出版)のメモ 急急如律令

Amazon Kindleダイレクト・パブリッシングでの電子出版や電子書籍の作成販売について、文章やイラストの作成や編集方法について書いています。

自分で小説サイトを作れないか

github.com

You: astroは独自のhtmlレンダーを使ってサイトを作れるか

ChatGPT: はい、Astroはかなり自由に独自HTMLレンダーを使えます。

Astro自体が:

  • 「HTMLを生成する仕組み」

  • 「コンポーネントを静的HTMLへ変換する仕組み」

なので、通常のMarkdown/MDXだけでなく、

  • 独自テンプレート

  • 独自レンダラー

  • 独自DSL

  • JSON→HTML

  • EPUBメタデータ→HTML

みたいな用途に非常に向いています。


代表的な方法

1. 普通に .astro で独自HTML生成

一番簡単。


---  
const book = Astro.props.book;  
---  
  
<html>  
  <body>  
    <article class="book">  
      <h1>{book.title}</h1>  
  
      <div class="author">  
        {book.author}  
      </div>  
  
      <a href={book.epub}>  
        Download  
      </a>  
    </article>  
  </body>  
</html>

これはAstro独自レンダリングそのものです。


2. JSON → HTML

大量EPUBサイトではこれが最強です。

---  
import books from "../data/books.json";  
---  
  
<ul>  
  {books.map((book) => (  
    <li>  
      <a href={`/books/${book.slug}`}>  
        {book.title}  
      </a>  
    </li>  
  ))}  
</ul>

データ駆動型。


3. 完全独自レンダラー

Node.jsコードでHTML文字列を生成可能。

exportfunctionrenderBook(book) {  
return`  
    <article class="book">  
      <h1>${escape(book.title)}</h1>  
      <p>${escape(book.author)}</p>  
    </article>  
  `;  
}

Astro側:


---  
import { renderBook } from "../lib/render";  
  
const html = renderBook(book);  
---  
  
<Fragment set:html={html} />

4. 独自マークアップ言語

たとえば:

#title: 吾輩は猫である  
#author: 夏目漱石  
  
本文

をパースしてHTML化。

exportfunctionrenderNovel(text) {  
returntext  
.replace(/^#title:(.*)$/gm, "<h1>$1</h1>")  
.replace(/^#author:(.*)$/gm, "<p>$1</p>");  
}

5. rehype/remark系を差し替え

Astroは内部で:

  • remark

  • rehype

を使えるので、
Markdown変換処理を拡張できます。

たとえば:

《ルビ》  
[#改ページ]

みたいな青空文庫記法を独自HTMLへ変換可能。


6. Astro Integrationで完全独自処理

かなり強力。

exportdefaultfunctionmyRenderer() {  
return {  
    name: "my-renderer",  
  
    hooks: {  
"astro:build:setup": () => {  
      }  
    }  
  };  
}

これで:

  • ビルドフック

  • HTML変換

  • 仮想ページ生成

などができます。


EPUBサイト用途なら特に強い構成

あなたの用途ならかなり相性が良いのは:

EPUB metadata  
   ↓  
独自HTML renderer  
   ↓  
Astro page

です。


例えばこういうことができる

青空文庫風レンダー

[#ここから2字下げ]

↓

<divclass="indent-2">

EPUBメタデータ自動HTML


{  
  "title": "...",  
  "author": "...",  
  "toc": [...]  
}

↓


<nav>

Astroの強み

Astroは:

  • ReactみたいなSPA前提ではない

  • HTML生成中心

  • SSG強い

  • Node.jsで前処理しやすい

ので、

「静的HTML生成器」

として非常に優秀です。


実はかなり自由

Astroは極端に言うと:

Node.js製静的サイトジェネレータ  
+  
HTMLテンプレートエンジン

として使えます。

なので:

  • Hugo

  • Jekyll

  • Eleventy

に近い使い方も可能です。


あなたの用途でおすすめ

大量EPUBサイトなら:

JSON  
 ↓  
独自parser  
 ↓  
HTML component  
 ↓  
Astro build

がかなりおすすめです。

特に:

  • EPUB情報

  • 著者分類

  • 青空文庫記法

  • ルビ

  • 目次

などを独自HTML化しやすいです。

情報源

ChatGPT:

Astroでは普通のNode.js/JavaScript関数をそのまま使えるので、
AozoraText2Html() を普通のJSモジュールとして切り出して import すれば使えます。

おすすめは:

src/lib/aozora/

以下に配置する構成です。


基本構成

src/  
├─ lib/  
│  └─ aozora/  
│     ├─ AozoraText2Html.js  
│     └─ parser.js  
│  
├─ pages/  
│  └─ books/  
│     └─ [slug].astro

AozoraText2Html.js

// src/lib/aozora/AozoraText2Html.js  
  
exportfunctionAozoraText2Html(  
text,  
cur_command="title"  
) {  
lethtml=text;  
  
// 改行  
html=html.replace(/\n/g, "<br>");  
  
// 見出し例  
html=html.replace(  
/^[#大見出し](.*?)$/gm,  
"<h1>$1</h1>"  
  );  
  
returnhtml;  
}

Astroページから使う


---  
import { AozoraText2Html }  
  from "../../lib/aozora/AozoraText2Html.js";  
  
const text = `  
[#大見出し]吾輩は猫である  
  
名前はまだ無い。  
`;  
  
const html = AozoraText2Html(text);  
---  
  
<article>  
  <Fragment set:html={html} />  
</article>

set:html が重要

Astroでは:


{html}

だとエスケープされます。

なので:


<Fragment set:html={html} />

を使います。

これは:

HTML


innerHTML

相当です。


大量EPUBサイトならおすすめ構成

実際には:

青空テキスト  
 ↓  
AozoraText2Html  
 ↓  
Layout.astro  
 ↓  
静的HTML

が良いです。


ページ単位生成

---  
// src/pages/books/[slug].astro  
  
import fs from "node:fs/promises";  
import path from "node:path";  
  
import { AozoraText2Html }  
  from "../../lib/aozora/AozoraText2Html.js";  
  
export async function getStaticPaths() {  
  return [  
    { params: { slug: "wagahai" } }  
  ];  
}  
  
const { slug } = Astro.params;  
  
const text = await fs.readFile(  
  path.resolve(`books/${slug}.txt`),  
  "utf-8"  
);  
  
const html = AozoraText2Html(text);  
---  
  
<html lang="ja">  
  <body>  
    <article class="aozora">  
      <Fragment set:html={html} />  
    </article>  
  </body>  
</html>

Astroと相性が良い理由

Astroは:

  • ビルド時Node.js実行可能

  • SSR可能

  • SSG可能

なので、

TXT → HTML変換

を非常にやりやすいです。

これは青空文庫系とかなり相性が良いです。


さらにおすすめ

parser と renderer を分離

最終的には:

text  
 ↓  
tokenize  
 ↓  
AST  
 ↓  
render HTML

構成にすると強いです。


例


[  
  {  
    type: "heading",  
    level: 1,  
    text: "吾輩は猫である"  
  },  
  {  
    type: "paragraph",  
    text: "名前はまだ無い。"  
  }  
]

↓

HTML


<h1>吾輩は猫である</h1>  
<p>名前はまだ無い。</p>

特に重要

大量ページでは:

文字列replace連打

だけだと限界が来ます。

青空文庫は:

  • 入れ子

  • 注記

  • ルビ

  • 傍点

  • 画像

  • 改ページ

があるので、
AST化すると管理しやすいです。


あなたの用途ならかなり良い構成

Aozora parser  
 ↓  
HTML renderer  
 ↓  
Astro static build  
 ↓  
Cloudflare Pages/GitHub Pages

がかなり強いです。

You:

小説サイトの場合は投稿日、章タイトル、タイトルなどフロントマッターをtxtファイルとしてあったほうがいいのか

ChatGPT:

はい、小説サイト用途なら
TXT本文とは別に「フロントマッター的メタデータ」を持たせたほうがかなり管理しやすいです。

特にAstroでは、

  • タイトル

  • 投稿日

  • 著者

  • 章タイトル

  • タグ

  • あらすじ

などをビルド時に使うことが多いので、
本文だけのTXTより圧倒的に便利です。


おすすめ構成

一番おすすめなのは:





title: 吾輩は猫である
author: 夏目漱石
date: 1905-01-01
tags:
- 小説
- 青空文庫

chapter: 上巻

本文...




みたいなYAML frontmatter付きテキストです。


Astroとの相性

これは実質:

  • Markdown

  • MDX

  • Astro Content Collections

と同じ構造なので扱いやすいです。


おすすめファイル形式

1. .md

最もAstro向き。




src/content/novels/wagahai.md






Markdown

title: 吾輩は猫である
author: 夏目漱石

date: 1905-01-01

名前はまだ無い。




ただし:

  • 青空文庫記法

  • 独自記法

が混ざるなら微妙。


2. .txt + frontmatter

かなりおすすめ。





title: 吾輩は猫である
author: 夏目漱石

date: 1905-01-01

[#大見出し]吾輩は猫である

名前はまだ無い。




青空文庫系と相性が良いです。


3. JSON分離

大量データ向き。




wagahai/
├─ meta.json
└─ body.txt




これは超大規模サイト向け。


小説サイトで重要なメタデータ

かなり重要です。


必須級

YAML

title:
author:
date:





ほぼ欲しくなる

YAML

description:
tags:
series:
cover:
updated:





章管理なら重要

YAML

chapter:
episode:
arc:
sort:





実際かなり便利

例えば:

YAML

date: 2026-05-26




があると:

  • 新着順

  • RSS

  • sitemap

  • OGP

  • SEO

全部やりやすくなります。


Astro Content Collections が強い

かなりおすすめ。

TypeScript

// src/content.config.ts

import { defineCollection, z }
from"astro:content";

constnovels=defineCollection({
type: "content",

schema: z.object({
title: z.string(),
author: z.string(),
date: z.date(),
tags: z.array(z.string()).optional(),
}),
});

exportconstcollections= {
novels,
};





するとこう使える

astro

import { getCollection }
from "astro:content";

const novels =

await getCollection("novels");





青空文庫パーサとの相性

かなり良いです。




frontmatter
↓
本文抽出
↓
Aozora parser
↓
HTML




構成にできます。


実装例

parser

JavaScript

importmatterfrom"gray-matter";

exportfunctionparseNovel(raw) {
const { data, content } =matter(raw);

return {
meta: data,
body: content,
};
}





Astroページ

astro

import fs from "node:fs/promises";
import matter from "gray-matter";

const raw = await fs.readFile(
"./novel.txt",
"utf-8"
);

const { data, content } =

matter(raw);





あなたの用途だと特におすすめ

かなり相性が良いのは:




frontmatter付きTXT
+
青空文庫記法




です。

理由:

  • Markdownと衝突しにくい

  • 青空記法を保持可能

  • 独自パーサを書きやすい

  • EPUB変換にも流用可能


最終的にかなり強い構成




novels/
├─ wagahai.txt
├─ sanshiro.txt
└─ ...




中身:





title:
author:
date:

tags:

青空文庫本文




↓




Astro build
↓
静的HTML小説サイト
↓
EPUB生成




この構成はかなり長期運用しやすいです。

You:

縦書き横書きなどを変更できる小説ビューワーとの組み合わせ方は

ChatGPT:

小説ビューワーとAstroを組み合わせる場合は、
Astroを「静的コンテンツ生成」、ビューワーを「クライアント側UI」に分離するとかなり綺麗に作れます。

特に:

  • 縦書き/横書き切替

  • フォント変更

  • 文字サイズ

  • 行間

  • テーマ

  • ページ送り

などはクライアントJS側でやるのが相性良いです。


おすすめ構成




Astro
├─ 小説HTML生成
├─ SEO
├─ ページ生成
└─ metadata

Viewer(JS)
├─ 縦書き
├─ 横書き
├─ フォント変更
├─ テーマ
├─ 設定保存
└─ ページ送り





Astro側は「本文HTMLだけ作る」

例えば:

astro

const html = AozoraText2Html(content);







Viewer側でCSS切替

これが一番重要です。


縦書きCSS

CSS

.viewer.vertical {
writing-mode: vertical-rl;
text-orientation: mixed;
}





横書きCSS

CSS

.viewer.horizontal {
writing-mode: horizontal-tb;
}





JSで切替

JavaScript

constviewer=
document.querySelector("#viewer");

document
.querySelector("#toggle")
.onclick = () => {

viewer.classList.toggle("vertical");
viewer.classList.toggle("horizontal");
};





HTML構造おすすめ

HTML

<divid="viewer"class="viewer vertical">

<divclass="novel-container">
<articleid="novel-body">






かなり重要

本文HTMLとUIを分離

おすすめ:




novel-body




は純粋HTML。

ビューワー側は:




viewer




として外側管理。

これで:

  • EPUB変換

  • PDF化

  • 検索

  • SSR

がやりやすいです。


Astro Islands が相性良い

Astroは:

astro




みたいに、

ビューワー部分だけJS化できます。

かなり向いてます。


React/Svelte併用おすすめ

例えば:




Astro
+
React Viewer




構成。


例

astro

import NovelViewer

from "../components/NovelViewer.jsx";





React側

JavaScript

exportdefaultfunctionNovelViewer({
html
}) {

const [vertical, setVertical] =
useState(true);

return (

);
}





localStorage保存

小説サイトではほぼ必須。

JavaScript

localStorage.setItem(
"writing-mode",
"vertical"
);





CSS変数で管理すると強い

CSS

.viewer {
--font-size: 18px;
--line-height: 2;
}

.viewerarticle {
font-size: var(--font-size);
line-height: var(--line-height);
}




JS:

JavaScript

viewer.style.setProperty(
"--font-size",
"22px"
);





大量小説サイトで重要

ページ分割

縦書きは長文で重くなります。

おすすめ:




1話単位HTML




です。


さらに重要

仮想スクロール

数十万文字では必要。

ただし最初は不要。


かなり相性が良い構成

あなたの用途だと:




青空TXT
↓
Astro build
↓
HTML生成
↓
Viewer JS
↓
縦書き切替




がベストに近いです。


特におすすめ

CSSだけで縦書き

最近のブラウザは:

CSS

writing-mode: vertical-rl;




が強いので、
昔ほど特殊実装不要です。


将来的におすすめ

Viewerを独立ライブラリ化




src/viewer/




に:

  • theme

  • pagination

  • ruby

  • tate-chu-yoko

などを分離するとかなり拡張しやすいです。


最終的にかなり強い構成




Astro
├─ SEO
├─ SSG
├─ 小説生成
└─ メタデータ

Viewer
├─ 縦書き
├─ 横書き
├─ フォント
├─ 設定保存
├─ ページ送り
└─ ダークモード




これは「小説家になろう系」にかなり近い構成になります。