htmx の設計思想を図解で理解する:HTML over the Wire とハイパーメディアの本質

Web 開発の世界では、複雑化が進む一方で「シンプルさ」を求める声が高まっています。そんな中、htmx という革新的なライブラリが注目を集めています。htmx は従来の SPA(Single Page Application)開発とは全く異なるアプローチを取り、HTML over the Wire というパラダイムを提案しています。
本記事では、htmx の設計思想を図解を交えながら詳しく解説し、なぜこのライブラリが現代の Web 開発において重要な選択肢となっているのかを探っていきます。JavaScript フレームワークの複雑さに疲れた開発者の方々にとって、新たな視点を提供できるでしょう。
背景
Web アプリケーション開発の複雑化
現代の Web アプリケーション開発は、かつてないほど複雑になっています。10 年前のシンプルな Web サイトと比較すると、今日のアプリケーションは多層アーキテクチャ、複雑な状態管理、多数の依存関係を抱えています。
以下の図は、現代的な Web アプリケーションの典型的な構成要素を示しています。
mermaidflowchart TD
frontend[フロントエンド] --> bundler[Webpack/Vite]
frontend --> framework[React/Vue/Angular]
frontend --> state[状態管理ライブラリ]
frontend --> router[クライアントサイドルーティング]
bundler --> build[ビルドプロセス]
framework --> component[コンポーネント]
state --> redux[Redux/Vuex/Pinia]
router --> spa[SPA設計]
backend[バックエンド] --> api[REST/GraphQL API]
backend --> auth[認証システム]
backend --> database[データベース]
api --> json[JSON レスポンス]
component --> virtual_dom[仮想DOM]
style frontend fill:#e1f5fe
style backend fill:#f3e5f5
この複雑性の増大は、以下のような問題を生み出しています。
開発者は複数の技術スタックを習得する必要があり、プロジェクトの立ち上げから本格的な開発までに長い時間がかかります。また、フロントエンドとバックエンドの分離により、データの流れを理解することが困難になっています。
SPA 時代の課題と制約
SPA が主流となった現在の Web 開発には、いくつかの根本的な課題があります。これらの課題は、開発効率や保守性に大きな影響を与えています。
mermaidflowchart LR
user[ユーザー] -->|リクエスト| spa[SPA アプリ]
spa -->|API 呼び出し| server[サーバー]
server -->|JSON データ| spa
spa -->|DOM 操作| view[画面更新]
spa --> state_mgmt[状態管理]
spa --> routing[クライアントルーティング]
spa --> component_tree[コンポーネントツリー]
state_mgmt --> complexity[複雑性増大]
routing --> history[履歴管理]
component_tree --> rerender[再レンダリング]
style complexity fill:#ffcdd2
style rerender fill:#ffcdd2
図で理解できる要点:
- SPA では JSON データを受け取り、クライアントサイドで DOM を構築
- 状態管理とコンポーネントツリーが複雑性を増大
- 再レンダリングやルーティングの制御が必要
この構造は特に、小規模から中規模のプロジェクトにおいて過剰なオーバーヘッドを生み出します。開発者は本来のビジネスロジックよりも、フレームワークの制約や設定に多くの時間を費やすことになるのです。
シンプルさを求める声
近年、開発コミュニティでは「シンプルさ」を重視する動きが活発化しています。この背景には、過度に複雑化した開発環境への反省があります。
多くの開発者が、以下のような状況に直面しています:
学習コストの増大: 新しいフレームワークやライブラリが次々と登場し、追いつくのが困難になっています。プロジェクトに参加する新しいメンバーは、まず複雑な技術スタックを理解する必要があります。
保守性の低下: 複雑なアーキテクチャは、バグの原因となりやすく、修正にも時間がかかります。特に状態管理が複雑になると、予期しない副作用が発生する可能性が高まります。
パフォーマンスの問題: 大きなバンドルサイズや複雑な処理により、アプリケーションの動作が重くなることがあります。
これらの課題を背景に、「Web の本質に立ち返る」というアプローチが注目されています。htmx は、まさにこの流れを代表するライブラリの一つです。
課題
JavaScript フレームワークの学習コスト
現代の JavaScript フレームワークを習得するには、膨大な時間と労力が必要です。React を例に取ると、基本的な概念だけでも以下のような要素を理解する必要があります。
以下のマップは、React 開発者が習得すべき概念の広がりを示しています。
mermaidmindmap
root((React開発))
基本概念
JSX
コンポーネント
Props
State
フック
useState
useEffect
useContext
カスタムフック
状態管理
ローカル状態
Context API
Redux
Zustand
ルーティング
React Router
動的ルーティング
認証ガード
ビルドツール
Webpack
Vite
Babel
TypeScript
新人開発者がプロダクションレベルの React アプリケーションを開発できるようになるまでには、通常 3〜6 ヶ月程度の学習期間が必要です。これは、HTML と CSS の基礎知識がある開発者でも同様です。
さらに困難なのは、これらの技術が急速に進化し続けていることです。React だけでも、クラスコンポーネントから関数コンポーネント、そして最近では Server Components まで、パラダイムが大きく変化しています。
状態管理の複雑性
SPA における状態管理は、多くの開発者が直面する最大の課題の一つです。アプリケーションが成長するにつれて、状態の管理は指数関数的に複雑になります。
以下の図は、典型的な SPA における状態管理の複雑さを表現しています。
mermaidstateDiagram-v2
[*] --> Loading
Loading --> Success : データ取得成功
Loading --> Error : データ取得失敗
Success --> Updating : データ更新中
Error --> Retrying : 再試行
Updating --> Success : 更新成功
Updating --> Error : 更新失敗
Retrying --> Success : 再試行成功
Retrying --> Error : 再試行失敗
Success --> Modal_Open : モーダル表示
Modal_Open --> Modal_Submitting : フォーム送信
Modal_Submitting --> Modal_Success : 送信成功
Modal_Submitting --> Modal_Error : 送信失敗
Modal_Success --> Success : モーダル閉じる
Modal_Error --> Modal_Open : エラー表示
図で理解できる要点:
- 単純な CRUD 操作でも多数の状態遷移が発生
- モーダルやフォームが絡むと状態が急激に複雑化
- エラーハンドリングを含めると状態パターンが爆発的に増加
この複雑性は、以下のような問題を引き起こします:
予期しない副作用: 一つの状態変更が、予想外の箇所に影響を与える可能性があります。例えば、ユーザー情報の更新が、ナビゲーションメニューの表示に影響を与えるといった具合です。
デバッグの困難さ: 状態が複雑になると、バグの原因を特定することが非常に困難になります。特に非同期処理が絡む場合、問題の再現すら困難な場合があります。
テストの複雑化: 全ての状態パターンをテストすることは現実的ではなく、重要な組み合わせを見逃すリスクが高まります。
バンドルサイズとパフォーマンス
現代の JavaScript フレームワークとその依存関係は、アプリケーションのバンドルサイズを大幅に増加させます。これは特にモバイルユーザーにとって深刻な問題です。
典型的な React アプリケーションのバンドルサイズの内訳を見てみましょう。
mermaidpie title React アプリケーションのバンドルサイズ内訳
"React ライブラリ" : 40
"ルーティングライブラリ" : 15
"状態管理ライブラリ" : 20
"UI コンポーネント" : 10
"ユーティリティライブラリ" : 10
"アプリケーションコード" : 5
実際のサイズ感:
- React + React DOM: 約 130KB(gzip 圧縮後)
- React Router: 約 20KB
- Redux Toolkit: 約 50KB
- UI ライブラリ(Material-UI など): 約 200KB
- その他のユーティリティ: 約 100KB
合計すると、基本的な React アプリケーションでも 500KB を超えるバンドルサイズになることは珍しくありません。これは、3G 回線では 15 秒以上のダウンロード時間を意味します。
さらに、JavaScript の解析と実行にも時間がかかります。特に古いデバイスでは、大きな JavaScript バンドルの実行に数秒かかることもあります。
解決策
htmx の基本理念
htmx は、Web 開発における根本的な問題に対して、シンプルで直感的な解決策を提案します。その核心にあるのは「HTML をより強力にする」という理念です。
htmx の基本理念を以下の図で表現します:
mermaidflowchart LR
html[HTML] -->|htmx 属性| enhanced[拡張された HTML]
enhanced --> ajax[AJAX リクエスト]
enhanced --> swap[DOM スワップ]
enhanced --> trigger[イベントトリガー]
ajax --> server[サーバー]
server -->|HTML 返却| response[HTML レスポンス]
response --> swap
style html fill:#e8f5e8
style enhanced fill:#c8e6c9
style response fill:#a5d6a7
図で理解できる要点:
- HTML に属性を追加するだけで AJAX 機能を実現
- サーバーからは HTML をそのまま返却
- 複雑な JavaScript 処理は不要
htmx の設計思想には、以下の原則があります:
HTML ファースト: HTML が Web の基礎であり、それを拡張することで必要な機能を実現します。新しいパラダイムを学ぶ必要がありません。
プログレッシブエンハンスメント: JavaScript が無効でも基本機能が動作し、有効な場合により良い体験を提供します。
サーバーサイド重視: 複雑な処理はサーバーサイドで行い、クライアントサイドはシンプルに保ちます。
宣言的アプローチ: HTML 属性により動作を宣言的に定義し、命令的な JavaScript を避けます。
HTML over the Wire とは
「HTML over the Wire」は、htmx が提唱する新しいアーキテクチャパターンです。従来の JSON API とクライアントサイドレンダリングの組み合わせとは対照的に、サーバーから HTML を直接送信します。
従来のアプローチと HTML over the Wire の比較を図示します:
mermaidsequenceDiagram
participant Browser as ブラウザ
participant Server as サーバー
participant DB as データベース
Note over Browser, DB: 従来のSPAアプローチ
Browser->>Server: GET /api/users
Server->>DB: SELECT * FROM users
DB-->>Server: ユーザーデータ
Server-->>Browser: JSON レスポンス
Browser->>Browser: JavaScript でDOM構築
Browser->>Browser: レンダリング
Note over Browser, DB: HTML over the Wire
Browser->>Server: GET /users (htmx)
Server->>DB: SELECT * FROM users
DB-->>Server: ユーザーデータ
Server->>Server: HTML テンプレート生成
Server-->>Browser: HTML レスポンス
Browser->>Browser: DOM 直接更新
このアプローチの利点:
シンプルさ: クライアントサイドでの複雑な処理が不要になります。データの変換やテンプレート処理はすべてサーバーサイドで完結します。
パフォーマンス: JSON をパースして DOM を構築する処理が不要なため、レスポンス時間が短縮されます。
SEO 対応: サーバーサイドで生成される HTML のため、検索エンジンの クローラーにとって理解しやすくなります。
キャッシュ効率: HTML レスポンスは CDN やブラウザキャッシュで効率的にキャッシュできます。
ハイパーメディアの力を活用する設計
htmx は、ハイパーメディア(HATEOAS: Hypermedia as the Engine of Application State)の概念を現代的に実装します。これは、アプリケーションの状態をハイパーメディア(リンクや フォーム)で表現するという考え方です。
ハイパーメディアドリブンなアプリケーションの構造を以下の図で示します:
mermaidflowchart TD
state1[初期状態] -->|ユーザーアクション| request[サーバーリクエスト]
request --> server_logic[サーバーサイド処理]
server_logic --> template[HTML テンプレート生成]
template --> state2[新しい状態(HTML)]
state2 --> action_links[利用可能なアクション]
action_links --> next_request[次のリクエスト]
server_logic --> business_logic[ビジネスロジック]
server_logic --> data_access[データアクセス]
style state1 fill:#e3f2fd
style state2 fill:#e8f5e8
style business_logic fill:#fff3e0
この設計の核心的な利点:
状態の一元管理: アプリケーションの状態はサーバーサイドで一元管理され、クライアントサイドでの状態の不整合が発生しません。
動的な UI: サーバーの状態に応じて、利用可能なアクション(ボタンやリンク)を動的に生成できます。例えば、管理者権限を持つユーザーにのみ「削除」ボタンを表示するといった制御が容易です。
ビジネスロジックの保護: 重要なビジネスロジックはサーバーサイドに保持され、クライアントサイドから直接アクセスできません。
一貫性の保証: データの整合性チェックや権限確認は、常にサーバーサイドで行われます。
具体例
従来の SPA と htmx の比較
実際のコードを通じて、従来の SPA と htmx のアプローチの違いを確認しましょう。シンプルなユーザーリストの表示と追加機能を例に取ります。
React での実装例:
まず、React での状態管理とコンポーネント定義から始まります:
typescriptimport React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
const UserList: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [newUser, setNewUser] = useState({ name: '', email: '' });
ユーザーデータの取得処理です:
typescriptuseEffect(() => {
const fetchUsers = async () => {
setLoading(true);
try {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
} catch (error) {
console.error('Error fetching users:', error);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
新しいユーザーの追加処理です:
typescriptconst handleAddUser = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
const addedUser = await response.json();
setUsers([...users, addedUser]);
setNewUser({ name: '', email: '' });
} catch (error) {
console.error('Error adding user:', error);
}
};
最後に、レンダリング部分です:
typescript return (
<div>
<h2>ユーザーリスト</h2>
{loading ? (
<p>読み込み中...</p>
) : (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
)}
<form onSubmit={handleAddUser}>
<input
value={newUser.name}
onChange={(e) => setNewUser({...newUser, name: e.target.value})}
placeholder="名前"
/>
<input
value={newUser.email}
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
placeholder="メールアドレス"
/>
<button type="submit">追加</button>
</form>
</div>
);
};
htmx での実装例:
同じ機能を htmx で実装すると、驚くほどシンプルになります:
html<div>
<h2>ユーザーリスト</h2>
<div
id="user-list"
hx-get="/users"
hx-trigger="load"
hx-indicator="#loading"
>
<!-- ユーザーリストがここに読み込まれます -->
</div>
<div id="loading" class="htmx-indicator">
読み込み中...
</div>
</div>
フォーム部分も非常にシンプルです:
html <form hx-post="/users"
hx-target="#user-list"
hx-swap="outerHTML">
<input name="name" placeholder="名前" required>
<input name="email" type="email" placeholder="メールアドレス" required>
<button type="submit">追加</button>
</form>
</div>
サーバーサイド(Node.js + Express):
javascript// ユーザーリスト表示
app.get('/users', (req, res) => {
const users = getUsersFromDatabase();
res.send(`
<div id="user-list">
<ul>
${users
.map(
(user) =>
`<li>${user.name} - ${user.email}</li>`
)
.join('')}
</ul>
</div>
`);
});
ユーザー追加の処理:
javascript// ユーザー追加
app.post('/users', (req, res) => {
const { name, email } = req.body;
addUserToDatabase({ name, email });
// 更新されたリストを返す
const users = getUsersFromDatabase();
res.send(`
<div id="user-list">
<ul>
${users
.map(
(user) =>
`<li>${user.name} - ${user.email}</li>`
)
.join('')}
</ul>
</div>
`);
});
HTML を直接返す API の実装
htmx を活用した API 設計では、JSON ではなく HTML フラグメントを返すことが重要です。この アプローチにより、クライアントサイドでの複雑な処理を排除できます。
動的なコンテンツ更新の例を見てみましょう:
javascript// 商品の詳細表示
app.get('/products/:id', (req, res) => {
const product = getProductById(req.params.id);
const reviews = getProductReviews(req.params.id);
res.send(`
<div class="product-detail">
<h3>${product.name}</h3>
<p class="price">¥${product.price.toLocaleString()}</p>
<p class="description">${product.description}</p>
${
product.inStock
? `<button hx-post="/cart/add/${product.id}"
hx-target="#cart-status"
class="btn-primary">
カートに追加
</button>`
: `<p class="out-of-stock">在庫切れ</p>`
}
<div class="reviews">
<h4>レビュー</h4>
${reviews
.map(
(review) => `
<div class="review">
<div class="rating">${'★'.repeat(
review.rating
)}</div>
<p>${review.comment}</p>
</div>
`
)
.join('')}
</div>
</div>
`);
});
リアルタイム更新を含む フォーム処理の例:
javascript// コメント投稿
app.post('/comments', (req, res) => {
const { postId, content, author } = req.body;
// バリデーション
if (!content.trim()) {
return res.send(`
<div class="error-message">
コメントを入力してください
</div>
`);
}
// コメント保存
const comment = saveComment({ postId, content, author });
// 新しいコメントと更新されたコメント一覧を返す
const allComments = getCommentsByPostId(postId);
res.send(`
<div id="comments-section">
<div class="success-message">
コメントが投稿されました
</div>
<div class="comments-list">
${allComments
.map(
(comment) => `
<div class="comment" data-comment-id="${
comment.id
}">
<div class="comment-header">
<span class="author">${comment.author}</span>
<span class="date">${formatDate(
comment.createdAt
)}</span>
</div>
<div class="comment-content">${
comment.content
}</div>
</div>
`
)
.join('')}
</div>
<form hx-post="/comments"
hx-target="#comments-section"
hx-swap="outerHTML">
<input type="hidden" name="postId" value="${postId}">
<textarea name="content" placeholder="コメントを入力"></textarea>
<input name="author" placeholder="お名前" required>
<button type="submit">投稿</button>
</form>
</div>
`);
});
HATEOAS 原則の実践
HATEOAS(Hypermedia as the Engine of Application State)は、アプリケーションの状態に応じて利用可能なアクションを動的に提供する設計原則です。htmx ではこれを自然に実装できます。
ユーザーの権限に応じた動的な UI の例:
javascript// 記事詳細表示(権限による表示制御)
app.get('/articles/:id', (req, res) => {
const article = getArticleById(req.params.id);
const user = getCurrentUser(req);
res.send(`
<article class="article-detail">
<header>
<h1>${article.title}</h1>
<div class="article-meta">
<span class="author">著者: ${
article.author
}</span>
<span class="date">${formatDate(
article.publishedAt
)}</span>
</div>
</header>
<div class="article-content">
${article.content}
</div>
<footer class="article-actions">
${generateArticleActions(article, user)}
</footer>
</article>
`);
});
function generateArticleActions(article, user) {
let actions = [];
// すべてのユーザーができるアクション
actions.push(`
<button hx-post="/articles/${article.id}/like"
hx-target="#like-count"
class="btn-like">
いいね (${article.likeCount})
</button>
`);
// ログインユーザーのみ
if (user) {
actions.push(`
<button hx-get="/articles/${article.id}/comment-form"
hx-target="#comment-form-container"
class="btn-comment">
コメントする
</button>
`);
}
// 記事の著者または管理者のみ
if (
user &&
(user.id === article.authorId || user.isAdmin)
) {
actions.push(`
<button hx-get="/articles/${article.id}/edit"
hx-target="main"
class="btn-edit">
編集
</button>
`);
actions.push(`
<button hx-delete="/articles/${article.id}"
hx-confirm="本当に削除しますか?"
hx-target="body"
hx-swap="outerHTML"
class="btn-delete">
削除
</button>
`);
}
return actions.join('');
}
ワークフロー状態に応じた動的アクションの例:
javascript// タスクの状態管理
app.get('/tasks/:id', (req, res) => {
const task = getTaskById(req.params.id);
const user = getCurrentUser(req);
res.send(`
<div class="task-detail" data-task-id="${task.id}">
<h2>${task.title}</h2>
<div class="task-status status-${task.status}">
${getStatusLabel(task.status)}
</div>
<p class="task-description">${task.description}</p>
<div class="task-actions">
${generateTaskActions(task, user)}
</div>
</div>
`);
});
function generateTaskActions(task, user) {
let actions = [];
switch (task.status) {
case 'draft':
if (user.id === task.assigneeId) {
actions.push(`
<button hx-patch="/tasks/${task.id}/start"
hx-target=".task-detail"
class="btn-primary">
作業開始
</button>
`);
}
break;
case 'in_progress':
if (user.id === task.assigneeId) {
actions.push(`
<button hx-patch="/tasks/${task.id}/complete"
hx-target=".task-detail"
class="btn-success">
完了
</button>
<button hx-patch="/tasks/${task.id}/pause"
hx-target=".task-detail"
class="btn-secondary">
一時停止
</button>
`);
}
break;
case 'completed':
if (user.isManager) {
actions.push(`
<button hx-patch="/tasks/${task.id}/approve"
hx-target=".task-detail"
class="btn-primary">
承認
</button>
<button hx-patch="/tasks/${task.id}/reject"
hx-target=".task-detail"
class="btn-danger">
差し戻し
</button>
`);
}
break;
}
return actions.join('');
}
まとめ
htmx は、現代の Web 開発における複雑性の問題に対して、革新的でありながら本質的な解決策を提供しています。HTML over the Wire とハイパーメディア駆動の設計により、開発者は本来のビジネスロジックに集中できるようになります。
htmx の主要な利点:
学習コストの大幅な削減: HTML と HTTP の知識があれば、すぐに動的な Web アプリケーションを構築できます。複雑な JavaScript フレームワークを習得する必要がありません。
保守性の向上: サーバーサイドでのテンプレート生成により、ロジックが一箇所に集約され、バグの発生を抑制できます。
パフォーマンスの改善: 小さなライブラリサイズ(約 10KB)と効率的な HTML レスポンスにより、高速な動作を実現します。
SEO とアクセシビリティ: サーバーサイドで生成される semantic な HTML により、検索エンジンとスクリーンリーダーの両方に優しいアプリケーションを構築できます。
htmx は「新しい技術」でありながら、Web の根本的な原則に立ち返る「温故知新」のアプローチです。複雑化した現代の Web 開発において、シンプルさと実用性を兼ね備えた選択肢として、今後さらなる注目を集めるでしょう。
特に、小規模から中規模のプロジェクトや、高い保守性が求められるエンタープライズアプリケーションにおいて、htmx は非常に有効な選択肢となります。JavaScript フレームワークの複雑さに疲れた開発者にとって、htmx は新たな可能性を開く扉となるはずです。
関連リンク
公式ドキュメント:
設計思想・記事:
実装例・チュートリアル:
コミュニティ:
- article
htmx の設計思想を図解で理解する:HTML over the Wire とハイパーメディアの本質
- article
htmx 技術ロードマップ 2025:SPA 脱却とサーバ駆動 UI の現在地
- article
htmx のエラーハンドリングとデバッグのコツ
- article
htmx で始めるWebアプリケーションの多言語・国際化対応の方法
- article
htmx の CSRF・セキュリティ対策実践ガイド
- article
htmx で作るチャット&メッセージング UI
- article
Zustand × Next.js の Hydration Mismatch を根絶する:原因別チェックリスト
- article
NestJS 依存循環(circular dependency)を断ち切る:ModuleRef と forwardRef の実戦対処
- article
MySQL ロック待ち・タイムアウトの解決:SHOW ENGINE INNODB STATUS の読み解き方
- article
WordPress を Docker で最速構築:開発/本番の環境差分をなくす手順
- article
Motion(旧 Framer Motion)でカクつき・ちらつきを潰す:レイアウトシフトと FLIP の落とし穴
- article
WebSocket 導入判断ガイド:SSE・WebTransport・長輪講ポーリングとの適材適所を徹底解説
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来