htmx とは?HTML だけでリッチな Web を実現する新時代フレームワーク

Web 開発の世界では、React、Vue.js、Angular といった複雑な JavaScript フレームワークが主流となっています。しかし、これらのフレームワークの学習コストの高さや複雑性に悩んでいる開発者も多いのではないでしょうか。
そんな中、注目を集めているのが「htmx」です。htmx は、HTML だけでリッチな Web アプリケーションを構築できる革新的なライブラリで、従来のフレームワークとは全く異なるアプローチを提案しています。わずか 14KB という軽量さでありながら、Ajax 通信、WebSocket、サーバー送信イベントなどの高度な機能を HTML 属性だけで実現できるのです。
今回は、htmx の基本概念から実際の実装例まで、初心者の方にもわかりやすくご紹介します。
htmx とは何か
htmx の基本概念
htmx(HyperText Markup eXtensions)は、HTML を拡張してリッチな Web アプリケーションを構築するための JavaScript ライブラリです。2020 年に登場したこのライブラリは、「HTML こそが Web アプリケーションの中心であるべき」という哲学に基づいて設計されています。
htmx の最大の特徴は、HTML 属性だけで Ajax 通信やイベント処理を実現できる点です。従来の JavaScript フレームワークのように、複雑な状態管理やコンポーネントシステムを学ぶ必要がありません。
HTML の拡張という発想
htmx は、HTML が本来持っている「ハイパーメディア」としての能力を拡張します。通常の HTML では、<form>
や<a>
タグしかサーバーとの通信ができませんが、htmx を使うとあらゆる HTML 要素がサーバーと通信できるようになります。
html<!-- 従来のHTML -->
<form action="/users" method="post">
<input name="name" type="text" />
<button type="submit">送信</button>
</form>
<!-- htmxを使用 -->
<div hx-post="/users" hx-target="#result">
<input name="name" type="text" />
<button hx-trigger="click">送信</button>
</div>
<div id="result"></div>
このように、どんな HTML 要素でもサーバーとの通信が可能になります。
JavaScript フレームワークとの違い
htmx と従来の JavaScript フレームワークの違いを表で整理してみましょう。
# | 項目 | htmx | React/Vue.js |
---|---|---|---|
1 | 学習コスト | 低い | 高い |
2 | ファイルサイズ | 14KB | 数百 KB〜数 MB |
3 | 状態管理 | サーバーサイド | クライアントサイド |
4 | 開発スタイル | HTML 中心 | JavaScript 中心 |
5 | SEO 対応 | 標準対応 | 追加設定が必要 |
htmx の大きな利点は、既存のサーバーサイドアプリケーションに簡単に組み込めることです。Rails や Django、Express などの Web フレームワークと自然に統合できます。
なぜ htmx が注目されるのか
従来の SPA 開発の課題
Single Page Application(SPA)の普及により、Web アプリケーションは格段にリッチになりました。しかし、同時に多くの課題も生まれています。
バンドルサイズの肥大化が深刻な問題となっています。React アプリケーションの初期バンドルサイズは、しばしば 1MB 以上になることがあります:
javascript// よくあるReactアプリケーションの依存関係
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"redux": "^4.2.0",
"@reduxjs/toolkit": "^1.9.0",
"axios": "^1.3.0",
"material-ui": "^5.0.0"
}
}
// 結果:バンドルサイズ 1.2MB以上
このようなバンドルサイズは、特にモバイルユーザーにとって大きな負担となります。
学習コストの問題
現代の JavaScript フレームワークは、習得すべき概念が非常に多くなっています。
React の場合の学習項目:
- JSX 記法
- コンポーネントライフサイクル
- Hooks(useState、useEffect、useContext 等)
- 状態管理ライブラリ(Redux、Zustand 等)
- ルーティング
- ビルドツール(Webpack、Vite 等)
新人エンジニアがこれらすべてを習得するには、通常 6 ヶ月から 1 年程度の期間が必要です。
開発体験の複雑化
モダンな JavaScript プロジェクトでは、開発環境の構築だけでも複雑になっています:
bash# 典型的なReactプロジェクトのセットアップ
yarn create react-app my-app
cd my-app
yarn add @reduxjs/toolkit react-redux
yarn add react-router-dom
yarn add @testing-library/react @testing-library/jest-dom
yarn add eslint-config-prettier prettier
yarn add -D @types/react @types/node
# エラー発生例
ERROR in ./src/App.js
Module not found: Error: Can't resolve 'react-router-dom' in '/Users/project/src'
このようなエラーは初心者にとって大きな障壁となります。
htmx の基本機能
ハイパーメディア駆動の考え方
htmx は「ハイパーメディア駆動アプリケーション」という概念に基づいています。これは、サーバーが HTML フラグメントを返し、クライアントがそれを適切な場所に配置するというシンプルな仕組みです。
従来の SPA が JSON を扱うのに対し、htmx は HTML を直接扱います:
javascript// 従来のSPA(JSON)
fetch('/api/users')
.then((response) => response.json())
.then((data) => {
// JSONをHTMLに変換する処理が必要
const html = data
.map((user) => `<li>${user.name}</li>`)
.join('');
document.getElementById('users').innerHTML = html;
});
html<!-- htmx(HTML) -->
<button hx-get="/users" hx-target="#users">
ユーザー一覧を取得
</button>
<ul id="users"></ul>
サーバー側では、JSON ではなく HTML フラグメントを返すだけです:
html<!-- サーバーが返すHTML -->
<li>田中太郎</li>
<li>佐藤花子</li>
<li>鈴木一郎</li>
主要な属性(hx-get, hx-post 等)
htmx の核心となるのは、HTML 要素に追加する特別な属性です。
hx-get 属性
GET リクエストを送信する最も基本的な属性です:
html<button hx-get="/api/time" hx-target="#current-time">
現在時刻を取得
</button>
<div id="current-time"></div>
hx-post 属性
POST リクエストでデータを送信します:
html<form hx-post="/api/comments" hx-target="#comments">
<textarea
name="content"
placeholder="コメントを入力"
></textarea>
<button type="submit">投稿</button>
</form>
<div id="comments"></div>
hx-target 属性
レスポンスを挿入する場所を指定します:
html<!-- IDで指定 -->
<button hx-get="/content" hx-target="#result">
クリック
</button>
<!-- CSSセレクターで指定 -->
<button hx-get="/content" hx-target=".content-area">
クリック
</button>
<!-- 最も近い親要素 -->
<button hx-get="/content" hx-target="closest div">
クリック
</button>
イベント処理の仕組み
htmx は豊富なイベント処理機能を提供しています。
hx-trigger 属性
リクエストを発火するイベントを指定できます:
html<!-- クリック時(デフォルト) -->
<button hx-get="/data">クリック</button>
<!-- マウスオーバー時 -->
<div hx-get="/preview" hx-trigger="mouseenter">
ホバーして預览
</div>
<!-- 入力値変更時 -->
<input
hx-get="/search"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
/>
<!-- 複数イベント -->
<div hx-get="/data" hx-trigger="click, keyup from:body">
多重事件
</div>
カスタムイベント
htmx は独自のイベントも発火します:
html<div hx-get="/data" hx-trigger="htmx:afterRequest">
リクエスト完了後に実行
</div>
javascript// JavaScriptでカスタムイベントを監視
document.addEventListener(
'htmx:afterRequest',
function (event) {
console.log('リクエストが完了しました:', event.detail);
}
);
実際に htmx を使ってみる
環境構築とセットアップ
htmx の導入は非常にシンプルです。CDN から読み込むか、Yarn でインストールするだけです。
CDN を使用する場合
html<!DOCTYPE html>
<html>
<head>
<title>htmx Example</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
<!-- htmxコードをここに記述 -->
</body>
</html>
Yarn でインストールする場合
bash# プロジェクトの初期化
mkdir htmx-demo
cd htmx-demo
yarn init -y
# htmxのインストール
yarn add htmx.org
# Express.jsも一緒にインストール(サーバー用)
yarn add express
yarn add -D nodemon
package.jsonの設定:
json{
"name": "htmx-demo",
"version": "1.0.0",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"htmx.org": "^1.9.10"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
シンプルな Ajax 通信の実装
基本的な Express.js サーバーを作成して htmx の動作を確認してみましょう。
server.js:
javascriptconst express = require('express');
const path = require('path');
const app = express();
const PORT = 3000;
// 静的ファイルの配信
app.use(express.static('public'));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// メインページ
app.get('/', (req, res) => {
res.sendFile(
path.join(__dirname, 'public', 'index.html')
);
});
// APIエンドポイント:現在時刻を返す
app.get('/api/time', (req, res) => {
const now = new Date().toLocaleString('ja-JP');
res.send(`<p>現在時刻: <strong>${now}</strong></p>`);
});
// APIエンドポイント:ユーザー一覧
app.get('/api/users', (req, res) => {
const users = [
{
id: 1,
name: '田中太郎',
email: 'tanaka@example.com',
},
{ id: 2, name: '佐藤花子', email: 'sato@example.com' },
{
id: 3,
name: '鈴木一郎',
email: 'suzuki@example.com',
},
];
const html = users
.map(
(user) =>
`<li>
<strong>${user.name}</strong>
<span>(${user.email})</span>
</li>`
)
.join('');
res.send(`<ul>${html}</ul>`);
});
app.listen(PORT, () => {
console.log(`サーバーがポート${PORT}で起動しました`);
});
public/index.html:
html<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>htmx Demo</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
}
button {
padding: 10px 20px;
margin: 10px 0;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
#result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>htmx デモ</h1>
<h2>基本的なGETリクエスト</h2>
<button hx-get="/api/time" hx-target="#time-result">
現在時刻を取得
</button>
<div id="time-result"></div>
<h2>ユーザー一覧の取得</h2>
<button hx-get="/api/users" hx-target="#users-result">
ユーザー一覧を表示
</button>
<div id="users-result"></div>
</body>
</html>
サーバーを起動して動作を確認します:
bashyarn dev
ブラウザで http://localhost:3000
にアクセスすると、ボタンをクリックするだけで Ajax 通信が実行され、結果がページに動的に挿入されます。
動的コンテンツの更新例
より実践的な例として、ToDo アプリケーションを作成してみましょう。
server.jsに追加:
javascript// メモリ上のToDoリスト(実際の開発ではデータベースを使用)
let todos = [
{ id: 1, text: 'htmxを学ぶ', completed: false },
{ id: 2, text: '記事を読む', completed: true },
];
let nextId = 3;
// ToDo一覧を取得
app.get('/api/todos', (req, res) => {
const html = todos
.map(
(todo) =>
`<li class="${todo.completed ? 'completed' : ''}">
<span>${todo.text}</span>
<button hx-patch="/api/todos/${todo.id}/toggle"
hx-target="closest li">
${todo.completed ? '未完了に戻す' : '完了'}
</button>
<button hx-delete="/api/todos/${todo.id}"
hx-target="closest li"
hx-swap="outerHTML">
削除
</button>
</li>`
)
.join('');
res.send(html);
});
// ToDo追加
app.post('/api/todos', (req, res) => {
const { text } = req.body;
if (!text || text.trim() === '') {
res.status(400).send(
`<p style="color: red;">
エラー: ToDoの内容を入力してください
</p>`
);
return;
}
const newTodo = {
id: nextId++,
text: text.trim(),
completed: false,
};
todos.push(newTodo);
// 新しいToDoアイテムのHTMLを返す
res.send(
`<li>
<span>${newTodo.text}</span>
<button hx-patch="/api/todos/${newTodo.id}/toggle"
hx-target="closest li">
完了
</button>
<button hx-delete="/api/todos/${newTodo.id}"
hx-target="closest li"
hx-swap="outerHTML">
削除
</button>
</li>`
);
});
// ToDo完了状態切り替え
app.patch('/api/todos/:id/toggle', (req, res) => {
const id = parseInt(req.params.id);
const todo = todos.find((t) => t.id === id);
if (!todo) {
res
.status(404)
.send(
'<p style="color: red;">ToDoが見つかりません</p>'
);
return;
}
todo.completed = !todo.completed;
res.send(
`<li class="${todo.completed ? 'completed' : ''}">
<span>${todo.text}</span>
<button hx-patch="/api/todos/${todo.id}/toggle"
hx-target="closest li">
${todo.completed ? '未完了に戻す' : '完了'}
</button>
<button hx-delete="/api/todos/${todo.id}"
hx-target="closest li"
hx-swap="outerHTML">
削除
</button>
</li>`
);
});
// ToDo削除
app.delete('/api/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
todos = todos.filter((t) => t.id !== id);
res.send(''); // 空のレスポンスで要素を削除
});
public/todo.html:
html<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>htmx ToDo App</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.completed {
text-decoration: line-through;
opacity: 0.6;
}
input[type='text'] {
width: 70%;
padding: 10px;
margin-right: 10px;
}
button {
padding: 8px 16px;
margin: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.add-btn {
background: #28a745;
color: white;
}
.toggle-btn {
background: #ffc107;
color: black;
}
.delete-btn {
background: #dc3545;
color: white;
}
#todo-list {
list-style: none;
padding: 0;
}
#todo-list li {
padding: 10px;
margin: 5px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>htmx ToDo アプリ</h1>
<!-- ToDoの追加フォーム -->
<form
hx-post="/api/todos"
hx-target="#todo-list"
hx-swap="beforeend"
>
<input
type="text"
name="text"
placeholder="新しいToDoを入力..."
required
/>
<button type="submit" class="add-btn">追加</button>
</form>
<!-- エラーメッセージ表示エリア -->
<div id="message"></div>
<!-- ToDo一覧 -->
<ul
id="todo-list"
hx-get="/api/todos"
hx-trigger="load"
>
<!-- ToDoアイテムがここに表示される -->
</ul>
</body>
</html>
この例では、以下の htmx 機能を活用しています:
- hx-swap 属性:要素の挿入方法を指定(beforeend、outerHTML 等)
- closestセレクター:最も近い親要素を対象に指定
- 複数の HTTP メソッド:GET、POST、PATCH、DELETE を使い分け
より高度な機能
ターゲット指定とスワップ機能
htmx の強力な機能の一つが、柔軟なターゲット指定とスワップ方法の選択です。
高度なターゲット指定
html<!-- 複数要素の同時更新 -->
<button
hx-get="/api/dashboard"
hx-target="#stats, #chart, #notifications"
>
ダッシュボード更新
</button>
<!-- 条件付きターゲット指定 -->
<form
hx-post="/api/login"
hx-target-error="#error-message"
hx-target="#success-message"
>
<input name="username" required />
<input name="password" type="password" required />
<button type="submit">ログイン</button>
</form>
<div id="error-message"></div>
<div id="success-message"></div>
スワップ方法の詳細制御
html<!-- innerHTML(デフォルト) -->
<button hx-get="/content" hx-target="#result">
innerHTML
</button>
<!-- outerHTML:要素自体を置換 -->
<button
hx-get="/content"
hx-target="#result"
hx-swap="outerHTML"
>
outerHTML
</button>
<!-- beforebegin:要素の直前に挿入 -->
<button
hx-get="/content"
hx-target="#result"
hx-swap="beforebegin"
>
beforebegin
</button>
<!-- afterend:要素の直後に挿入 -->
<button
hx-get="/content"
hx-target="#result"
hx-swap="afterend"
>
afterend
</button>
<!-- delete:要素を削除 -->
<button
hx-delete="/api/item/1"
hx-target="#item-1"
hx-swap="delete"
>
削除
</button>
スワップのタイミング制御
html<!-- 1秒後にスワップ -->
<button
hx-get="/delayed-content"
hx-target="#result"
hx-swap="innerHTML swap:1s"
>
遅延更新
</button>
<!-- スクロール位置を維持 -->
<button
hx-get="/long-content"
hx-target="#content"
hx-swap="innerHTML show:none"
>
スクロール位置維持
</button>
イベントハンドリング
htmx は豊富なイベント処理機能を提供しています。
高度なトリガー設定
html<!-- 複合条件 -->
<input
hx-get="/search"
hx-trigger="keyup changed delay:300ms, search"
hx-target="#search-results"
/>
<!-- 条件付きトリガー -->
<div
hx-get="/notifications"
hx-trigger="every 30s [!document.hidden]"
hx-target="#notifications"
>
<!-- ページが表示されている間のみ30秒ごとに実行 -->
</div>
<!-- 要素が画面に入った時 -->
<div
hx-get="/lazy-content"
hx-trigger="intersect once"
hx-target="this"
>
画面に表示されたらコンテンツをロード
</div>
カスタムイベントの活用
javascript// JavaScriptからカスタムイベントを発火
document
.getElementById('refresh-button')
.addEventListener('click', () => {
htmx.trigger('#dashboard', 'custom:refresh');
});
html<!-- カスタムイベントを受信 -->
<div
id="dashboard"
hx-get="/api/dashboard"
hx-trigger="custom:refresh"
hx-target="this"
>
ダッシュボードコンテンツ
</div>
エラーハンドリング
html<!-- エラー時の処理 -->
<form hx-post="/api/submit" hx-target="#result">
<input name="data" required />
<button type="submit">送信</button>
</form>
<div id="result"></div>
<script>
// エラーイベントの監視
document.addEventListener(
'htmx:responseError',
function (event) {
const errorDiv = document.getElementById('result');
if (event.detail.xhr.status === 400) {
errorDiv.innerHTML = `
<div style="color: red;">
入力エラー: ${event.detail.xhr.responseText}
</div>
`;
} else if (event.detail.xhr.status === 500) {
errorDiv.innerHTML = `
<div style="color: red;">
サーバーエラーが発生しました。しばらく待ってから再試行してください。
</div>
`;
}
}
);
// ネットワークエラーの処理
document.addEventListener(
'htmx:sendError',
function (event) {
document.getElementById('result').innerHTML = `
<div style="color: red;">
ネットワークエラー: 接続を確認してください
</div>
`;
}
);
</script>
CSS アニメーションとの連携
htmx は CSS アニメーションと組み合わせることで、滑らかなユーザーインターフェースを実現できます。
フェードイン/フェードアウト効果
css/* CSS */
.fade-in {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.fade-in.htmx-added {
opacity: 1;
}
.fade-out {
opacity: 1;
transition: opacity 0.3s ease-in-out;
}
.fade-out.htmx-swapping {
opacity: 0;
}
html<!-- HTML -->
<button
hx-get="/api/content"
hx-target="#animated-content"
hx-swap="innerHTML transition:true"
>
アニメーション付きで更新
</button>
<div id="animated-content" class="fade-in fade-out">
初期コンテンツ
</div>
スライドアニメーション
css.slide-down {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.slide-down.htmx-added {
max-height: 1000px;
}
.slide-up {
max-height: 1000px;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.slide-up.htmx-swapping {
max-height: 0;
}
ローディングインジケーター
css.htmx-request {
opacity: 0.6;
pointer-events: none;
}
.htmx-request::after {
content: ' 読み込み中...';
color: #666;
}
.spinner {
display: none;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
margin-left: 10px;
}
.htmx-request .spinner {
display: inline-block;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
html<button hx-get="/api/slow-operation" hx-target="#result">
時間のかかる処理
<span class="spinner"></span>
</button>
<div id="result"></div>
実践的なアニメーション例
html<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>htmx アニメーション例</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 10px 0;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.notification {
padding: 15px;
border-radius: 4px;
margin: 10px 0;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.notification.htmx-added {
transform: translateX(0);
}
.success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
</style>
</head>
<body>
<h1>アニメーション付きhtmx</h1>
<button
hx-post="/api/notify/success"
hx-target="#notifications"
hx-swap="beforeend"
>
成功通知を表示
</button>
<button
hx-post="/api/notify/error"
hx-target="#notifications"
hx-swap="beforeend"
>
エラー通知を表示
</button>
<div id="notifications"></div>
</body>
</html>
server.jsに追加:
javascript// 通知API
app.post('/api/notify/:type', (req, res) => {
const type = req.params.type;
const messages = {
success: '操作が正常に完了しました!',
error: 'エラーが発生しました。再試行してください。',
};
const message = messages[type] || '不明な通知';
res.send(`
<div class="notification ${type}">
${message}
<button onclick="this.parentElement.remove()"
style="float: right; background: none; border: none;">
×
</button>
</div>
`);
});
まとめ
htmx は、Web 開発の複雑さを大幅に軽減し、HTML の本来の力を最大限に活用できる革新的なライブラリです。わずか 14KB という軽量さでありながら、従来の JavaScript フレームワークに匹敵する機能を提供します。
htmx の主な利点
学習コストの低さ:HTML の知識があれば、すぐに使い始めることができます。複雑な状態管理やコンポーネントシステムを学ぶ必要がありません。
既存プロジェクトとの相性:Rails、Django、Express.js など、既存のサーバーサイドフレームワークと自然に統合できます。段階的な導入も可能です。
パフォーマンス:小さなバンドルサイズと効率的な HTML 更新により、高いパフォーマンスを実現できます。
SEO フレンドリー:サーバーサイドレンダリングベースのため、検索エンジン最適化が容易です。
適用を検討すべき場面
htmx は以下のような場面で特に威力を発揮します:
- 既存のサーバーサイドアプリケーションの機能強化
- プロトタイプ開発や MVP(Minimum Viable Product)の構築
- シンプルな CRUD アプリケーション
- 学習コストを抑えたいチーム開発
一方で、複雑な状態管理が必要な大規模 SPA や、リアルタイム性が重要なアプリケーションでは、従来の JavaScript フレームワークの方が適している場合もあります。
htmx は、Web 開発の新しい選択肢として、多くの開発者に新鮮な開発体験を提供してくれるでしょう。ぜひ一度試してみて、その魅力を実感してみてください。
関連リンク
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
- review
人類はなぜ地球を支配できた?『サピエンス全史 上巻』ユヴァル・ノア・ハラリが解き明かす驚愕の真実
- review
え?世界はこんなに良くなってた!『FACTFULNESS』ハンス・ロスリングが暴く 10 の思い込みの正体
- review
瞬時に答えが出る脳に変身!『ゼロ秒思考』赤羽雄二が贈る思考力爆上げトレーニング
- review
関西弁のゾウに人生変えられた!『夢をかなえるゾウ 1』水野敬也が教えてくれた成功の本質