Notionで作成した仕様書をAWSで自動公開するシステムを作ってみた(後編)
この記事は Supershipグループ Advent Calendar 2025 の 18日目の記事です。
TL;DR
-
NotionエクスポートをS3に自動公開するWebアプリを開発しました
- FastAPI + Next.js 15で構築
- S3 + CloudFrontでドキュメントをホスティング
- 後編ではS3自動アップロード機能の実装を解説(前編で差分分析ツールを解説)
- HTML加工(CSS注入・パンくず自動生成)、S3アップロード、CloudFrontキャッシュ無効化など実装のポイントを紹介
はじめに
前編ではNotionからエクスポートしたHTMLの課題と、その解決方法について説明しました。後編では、実際に構築したフロントエンド・バックエンドシステムの全体像と、各処理の詳細について解説します。
システム全体構成
今回構築したシステムは、Next.js(フロントエンド)とFastAPI(バックエンド)で構成されています。
フロントエンドとバックエンドの連携フロー
ユーザーがドキュメントを公開するまでの流れを、リクエストとレスポンスを交えて説明します。
ステップ1: バリデーション(検証)
フロントエンドでZIPファイルと対象の案件を選択すると、自動的にバリデーションAPIが呼び出されます。
リクエスト例(フロントエンド側):
const formData = new FormData();
formData.append("file", file);
formData.append("case", caseName);
const response = await fetch("/app/validation", {
method: "POST",
body: formData,
});
バックエンドでの処理:
- ZIPファイルをメモリ上で展開
- 含まれるHTMLファイルからページIDを抽出
- S3上の既存HTMLファイルと照合し、一致率を計算
- 一致率が60%以上なら成功、未満なら警告を返す
閾値を60%とした理由は特にないですw
この検証により、誤った案件へのアップロードを防止しています。
レスポンス例:
{
"status": "success",
"session_id": "project_session_20241215_143052",
"match_rate": 85.5,
"pages": [
{
"file_path": "API仕様書 abc123.html",
"page_name": "API仕様書",
"page_id": "abc123",
"filename": "API仕様書 abc123.html"
}
]
}
ステップ2: S3アップロード
検証が成功すると、ユーザーは公開するページを選択してアップロードを実行できます。
リクエスト例:
const formData = new FormData();
formData.append("file", file);
formData.append("case", caseName);
formData.append("session_id", sessionId);
formData.append("selected_pages", JSON.stringify(selectedPageIds));
const response = await fetch("/app/s3-upload-docs", {
method: "POST",
body: formData,
});
レスポンス例:
{
"status": "success",
"message": "Notion docs uploaded to S3 and CloudFront cache invalidated successfully",
"cloudfront": {
"status": "completed",
"distribution_id": "E1234567890",
"invalidation_id": "I1234567890"
}
}
HTML加工処理の詳細
バックエンドでは、NotionからエクスポートしたHTMLに対して複数の加工処理を行います。
処理の流れ
CSS注入の実装
NotionエクスポートのHTMLは、そのままでは見づらいスタイルになっています。カスタムCSSを注入して、表示を改善します。
注入するCSSの主なスタイル:
/* パンくずナビゲーション - ページ上部に固定表示 */
.breadcrumb-nav {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 1000 !important;
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(10px) !important;
}
/* テーブルの横スクロール対策 */
table.simple-table {
table-layout: fixed !important;
}
table.simple-table th,
table.simple-table td {
overflow-wrap: anywhere !important;
word-break: break-word !important;
}
/* 最終更新日のスタイル */
.last-updated-date-h1 {
display: block !important;
font-size: 0.875rem !important;
color: #666 !important;
}
CSS注入の処理:
バックエンドでは、BeautifulSoupを使ってHTMLを解析し、<head>タグ内に<style>要素を追加します。
def inject_css(soup, css_injection_path):
"""カスタムCSSを注入"""
style_elem = soup.new_tag('style')
style_elem.string = css_injection_path.read_text(encoding='utf-8')
head = soup.find('head')
if head:
head.append(style_elem)
パンくずリスト自動生成
Notionのページ階層構造から、自動的にパンくずナビゲーションを生成します。
生成されるHTML:
<div class="breadcrumb-nav">
<a href="../index.html">仕様書TOP</a>
<span class="breadcrumb-separator">></span>
<a href="index.html">API仕様書</a>
<span class="breadcrumb-separator">></span>
<span>認証API</span>
</div>
パンくず生成のロジック:
- 現在のHTMLファイルのパスから階層を取得
- 各階層に対応するindex.htmlまたは親HTMLを探索
- 相対パスでリンクを生成
- 現在のページ名は最後にリンクなしで表示
Notionのエクスポートでは、ページ階層がディレクトリ構造として表現されます。例えば「仕様書TOP > API仕様書 > 認証API」という階層は、以下のようなディレクトリ構造になります:
プライベート、シェア/
├── index.html # 仕様書TOP
├── API仕様書 abc123/
│ ├── index.html # API仕様書
│ └── 認証API def456.html # 認証API
この構造を解析して、適切な相対パスでリンクを生成しています。
最終更新日の自動追加
各ページのh1タグの下に、アップロード日時を「最終更新日」として自動追加します。これにより、ドキュメントの鮮度が一目でわかるようになります。
def inject_last_updated_date(soup, html_file):
"""最終更新日をHTMLに追加"""
current_date = datetime.now().strftime("%Y年%m月%d日")
first_h1 = soup.find('h1')
if first_h1:
date_span = soup.new_tag('span')
date_span['class'] = 'last-updated-date-h1'
date_span.string = f"最終更新日: {current_date}"
first_h1.append(date_span)
S3アップロードとバックアップ
加工が完了したHTMLファイルは、AWS CLIを使ってS3にアップロードします。
アップロード処理
ユーザーが選択したページのみをアップロードする場合と、全ファイルを同期する場合の2パターンに対応しています。
選択アップロード:
特定のページだけ更新したい場合に使用します。index.htmlは常にアップロード対象に含まれます。
全ファイル同期:
aws s3 syncコマンドで、ローカルとS3の差分を同期します。--deleteオプションにより、削除されたページもS3から自動的に削除されます。
自動バックアップ
アップロード時に、日付付きのバックアップディレクトリにも同時保存します。これにより、過去のバージョンを保持し、必要に応じて復元できます。
s3://project-s4-docs/
├── index.html # 現在公開中
├── api-spec.html
├── ...
└── backup/
├── 20241201/ # 12月1日時点のバックアップ
│ ├── index.html
│ └── ...
├── 20241208/ # 12月8日時点のバックアップ
└── 20241215/ # 本日のバックアップ
CloudFrontキャッシュの自動無効化
S3へのアップロードが完了しても、CloudFrontのキャッシュが残っていると古いコンテンツが配信され続けます。そこで、アップロード完了後に自動的にキャッシュを無効化します。
無効化処理の流れ
- ディストリビューション検索: 案件名を含むCloudFrontディストリビューションを自動検索
-
キャッシュ無効化実行: 全パス(
/*)を対象に無効化リクエストを送信 - 完了待機: 無効化が完了するまで待機(最大10分)
# 1. 対象のディストリビューションを検索
response = cloudfront.list_distributions()
for distribution in response['DistributionList']['Items']:
if <ディストリビーション名> in distribution.get('Comment', ''):
distribution_id = distribution['Id']
break
# 2. キャッシュ無効化を実行
cloudfront.create_invalidation(
DistributionId=distribution_id,
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': ['/*'] # 全パスを無効化
},
'CallerReference': f'invalidation-{timestamp}'
}
)
# 3. 完了を待機
while elapsed_time < 600:
status = cloudfront.get_invalidation(...)
if status['Invalidation']['Status'] == 'Completed':
break
time.sleep(5)
無効化の所要時間
CloudFrontのキャッシュ無効化は通常1〜2分で完了しますが、混雑時は数分かかることもあります。システムでは最大10分まで待機し、完了を確認してからユーザーに通知します。
まとめ
本システムにより、以下のワークフローが自動化されました:
- NotionでドキュメントをHTML形式でエクスポート
- Webアプリにアップロード(検証→公開)
- 自動的にHTML加工・S3アップロード・キャッシュ削除
- 即座に最新ドキュメントが閲覧可能に
- 誰でも直感的にアップロードできるように
従来は手作業で行っていたHTML加工やアップロード作業が、ボタン1つで完了するようになりました。
本システムの構築を通じて、NotionとAWSを組み合わせたドキュメント管理の可能性を実感しました。同様の課題を抱えている方の参考になれば幸いです。


