htmx で二重送信が起きる/起きない問題の完全対処:trigger と disable パターン
Web アプリケーション開発において、フォーム送信やボタンクリック時の「二重送信」は古くて新しい問題です。htmx を使えば少ないコードで動的な UI が実現できる一方、デフォルトの挙動を理解していないと、意図せず二重送信が発生したり、逆に期待通りに防げなかったりします。
本記事では、htmx における二重送信問題の 根本原因、発生パターン、そして trigger と disable を活用した完全対処法 まで、初心者の方にもわかりやすく解説します。
背景
htmx とは
htmx は、HTML 属性だけで Ajax リクエストを実現できる軽量な JavaScript ライブラリです。従来の JavaScript フレームワークと比べて、以下のような特徴があります。
- HTML 中心の記述:
hx-get、hx-postなどの属性でリクエストを定義 - サーバー駆動: サーバーから返される HTML を直接 DOM に挿入
- 軽量: 約 14KB の小さなライブラリサイズ
以下の図は、htmx の基本的な動作フローを示しています。
mermaidflowchart LR
user["ユーザー"] -->|クリック/入力| elem["HTML 要素<br/>(hx-* 属性)"]
elem -->|イベント発火| htmx["htmx ライブラリ"]
htmx -->|HTTP リクエスト| server["サーバー"]
server -->|HTML レスポンス| htmx
htmx -->|DOM 更新| page["ページ"]
page -->|表示| user
図で理解できる要点:
- htmx はイベント(クリック、入力など)をトリガーに自動的にリクエストを送信
- サーバーから返された HTML を直接 DOM に反映
- JavaScript コードを書かずに動的な UI を実現
二重送信問題の一般的な背景
二重送信とは、ユーザーの 1 回の操作に対して、意図せず複数回のリクエストがサーバーに送られてしまう現象です。これにより以下のような問題が発生します。
- データの重複登録: 同じ注文が 2 回処理される
- サーバー負荷の増加: 不要なリクエストでリソースを消費
- UX の低下: ユーザーが混乱する
従来の Web アプリケーションでは、JavaScript で手動制御したり、サーバー側でトークンを使った制御を行ったりしていました。htmx でも同様の問題が発生しますが、独自の解決策が用意されています。
課題
htmx で二重送信が「起きる」ケース
htmx を使っていると、以下のようなケースで二重送信が発生することがあります。
ケース 1: ボタンの連打
最もよくあるのが、送信ボタンを何度もクリックしてしまうケースです。
html<!-- 問題のあるコード例 -->
<button hx-post="/api/submit" hx-target="#result">
送信
</button>
このコードでは、ユーザーがボタンを素早く 2 回クリックすると、2 つのリクエストが送信されてしまいます。
ケース 2: フォーム送信とボタンのイベント重複
フォームと送信ボタンの両方に htmx 属性を付けた場合、イベントが重複して発火することがあります。
html<!-- イベントが重複する例 -->
<form hx-post="/api/submit">
<input type="text" name="username" />
<button type="submit" hx-post="/api/submit">送信</button>
</form>
上記の場合、フォームの submit イベントとボタンのクリックイベントが両方発火し、2 回リクエストが送られる可能性があります。
ケース 3: カスタムイベントとの競合
htmx は hx-trigger でカスタムイベントを設定できますが、イベントの発火タイミングが重複すると二重送信につながります。
html<!-- カスタムイベントでの重複例 -->
<button
hx-post="/api/submit"
hx-trigger="click, customEvent"
>
送信
</button>
click イベントで customEvent も発火するような実装だと、1 回のクリックで 2 回リクエストが送られます。
htmx で二重送信が「起きない」はずなのに起きるケース
一方で、「htmx は自動的に二重送信を防ぐ」と思い込んで実装すると、意図しない挙動に遭遇することがあります。
デフォルトの挙動への誤解
htmx はリクエスト中に 同じ要素からの再リクエストを自動的にブロック します。しかし、これは以下の条件を満たす場合のみです。
- 同一の要素 からのリクエストであること
- 前のリクエストが完了していない こと
以下の図は、htmx のデフォルト動作における二重送信防止の仕組みを示しています。
mermaidstateDiagram-v2
[*] --> Idle: 初期状態
Idle --> Requesting: クリック①
Requesting --> Idle: レスポンス受信
Requesting --> Requesting: クリック②<br/>(ブロックされる)
note right of Requesting
リクエスト中は同じ要素からの
再リクエストを自動ブロック
end note
図で理解できる要点:
- リクエスト中(Requesting 状態)は同一要素からの再クリックを無視
- レスポンス受信後(Idle 状態)に戻ると再度リクエスト可能
- ただし、この仕組みは「同一要素」に限定される
複数要素での同時リクエスト
別々の要素から同時にリクエストが送られると、htmx のデフォルト防止機能は働きません。
html<!-- 別要素からの同時リクエストは防げない -->
<button id="btn1" hx-post="/api/submit">ボタン 1</button>
<button id="btn2" hx-post="/api/submit">ボタン 2</button>
解決策
trigger パターンによる制御
hx-trigger 属性を使うことで、リクエストの発火タイミングを細かく制御できます。
once 修飾子で 1 回限りの実行
最も単純な解決策は、once 修飾子を使って 1 回だけ実行させる方法です。
html<!-- once 修飾子の使用 -->
<button hx-post="/api/submit" hx-trigger="click once">
送信(1回のみ)
</button>
この設定により、ボタンは最初のクリックでのみリクエストを送信し、以降のクリックは無視されます。
適用シーン: 登録処理、決済処理など、絶対に 1 回しか実行してはいけない操作
throttle 修飾子で連打を制限
一定時間内の連打を防ぐには、throttle 修飾子を使います。
html<!-- throttle で 1 秒間に 1 回まで制限 -->
<button
hx-post="/api/submit"
hx-trigger="click throttle:1s"
>
送信
</button>
throttle:1s と指定すると、クリックから 1 秒間は次のリクエストを送信しません。
適用シーン: 検索フィルター、ページネーションなど、短時間の連続操作が想定される UI
debounce 修飾子で入力完了を待つ
入力フォームなど、ユーザーの操作が落ち着いてからリクエストを送りたい場合は debounce を使います。
html<!-- 入力停止後 500ms でリクエスト -->
<input
type="text"
name="search"
hx-get="/api/search"
hx-trigger="keyup changed debounce:500ms"
hx-target="#results"
/>
debounce:500ms により、最後の入力から 500 ミリ秒経過後にリクエストが送られます。
適用シーン: オートコンプリート、インクリメンタルサーチ
以下の図は、throttle と debounce の違いを視覚化したものです。
mermaidsequenceDiagram
participant User as ユーザー
participant Throttle as throttle:1s
participant Debounce as debounce:500ms
User->>Throttle: クリック①
Throttle->>Throttle: リクエスト送信
User->>Throttle: クリック② (0.3秒後)
Note right of Throttle: ブロック (1秒未満)
User->>Throttle: クリック③ (1.2秒後)
Throttle->>Throttle: リクエスト送信
User->>Debounce: 入力①
Note right of Debounce: 待機開始
User->>Debounce: 入力② (0.2秒後)
Note right of Debounce: タイマーリセット
User->>Debounce: 入力③ (0.3秒後)
Note right of Debounce: タイマーリセット
Note right of Debounce: 0.5秒経過
Debounce->>Debounce: リクエスト送信
図で理解できる要点:
- throttle: 一定間隔で定期的にリクエストを送る(連打の頻度を下げる)
- debounce: 操作が落ち着いてから 1 回だけリクエストを送る(入力完了を待つ)
disable パターンによる制御
hx-disabled-elt でボタンを無効化
リクエスト中にボタンを無効化することで、視覚的にも機能的にも二重送信を防げます。
html<!-- リクエスト中はボタンを無効化 -->
<button hx-post="/api/submit" hx-disabled-elt="this">
送信
</button>
hx-disabled-elt="this" により、リクエスト開始時に disabled 属性が付与され、レスポンス受信後に自動的に解除されます。
複数要素を同時に無効化
フォーム全体を無効化したい場合は、セレクタを使って複数要素を指定できます。
html<!-- フォーム内の全入力要素を無効化 -->
<form hx-post="/api/submit">
<input type="text" name="username" />
<input type="email" name="email" />
<button type="submit" hx-disabled-elt="closest form">
送信
</button>
</form>
closest form により、最も近い <form> 要素内の全入力要素が無効化されます。
CSS でローディング状態を表現
htmx-request クラスを使うと、リクエスト中の視覚的フィードバックを提供できます。
html<!-- リクエスト中の見た目を変更 -->
<button hx-post="/api/submit" hx-disabled-elt="this">
送信
</button>
css/* リクエスト中のスタイル */
.htmx-request {
opacity: 0.5;
cursor: wait;
}
/* ボタンが無効化された時のスタイル */
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
htmx は自動的に .htmx-request クラスをリクエスト中の要素に付与するため、CSS で簡単にローディング状態を表現できます。
組み合わせパターン:最強の二重送信対策
実務では、trigger と disable を組み合わせることで、より堅牢な二重送信対策が実現できます。
html<!-- trigger + disable の組み合わせ -->
<button
hx-post="/api/submit"
hx-trigger="click throttle:1s"
hx-disabled-elt="this"
hx-indicator="#spinner"
>
<span>送信</span>
<span id="spinner" class="htmx-indicator">
処理中...
</span>
</button>
css/* インジケーターの制御 */
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: inline;
}
.htmx-request span:not(.htmx-indicator) {
display: none;
}
この実装では、以下の多層防御が実現されています。
- throttle: 1 秒間に 1 回までに制限
- disabled: リクエスト中はボタンを物理的に無効化
- indicator: ユーザーに処理中であることを明示
以下の図は、組み合わせパターンでの防御層を示しています。
mermaidflowchart TD
click["ユーザークリック"] --> throttle{"throttle<br/>1秒以内?"}
throttle -->|Yes| block1["ブロック<br/>(第1層防御)"]
throttle -->|No| disabled{"ボタン<br/>disabled?"}
disabled -->|Yes| block2["ブロック<br/>(第2層防御)"]
disabled -->|No| request["リクエスト送信"]
request --> disableBtn["ボタン無効化<br/>インジケーター表示"]
disableBtn --> wait["レスポンス待機"]
wait --> response["レスポンス受信"]
response --> enable["ボタン有効化<br/>インジケーター非表示"]
enable --> idle["待機状態"]
図で理解できる要点:
- 第 1 層(throttle)で短時間の連打を防御
- 第 2 層(disabled)でリクエスト中の操作を防御
- インジケーターでユーザーに状態を明示し、誤操作を防止
具体例
例 1: EC サイトの購入ボタン
EC サイトでは、購入ボタンの二重送信は致命的です。以下は、購入処理に特化した実装例です。
HTML 実装
html<!-- 購入ボタンの実装 -->
<form id="purchaseForm" hx-post="/api/purchase">
<div class="product-info">
<h3>商品: プレミアムプラン</h3>
<p>価格: ¥9,800</p>
</div>
<input
type="hidden"
name="product_id"
value="premium-001"
/>
<input type="hidden" name="quantity" value="1" />
</form>
html <button
type="submit"
hx-trigger="click once"
hx-disabled-elt="this"
hx-indicator="#purchase-spinner"
hx-target="#purchase-result">
<span class="button-text">購入する</span>
<span id="purchase-spinner" class="htmx-indicator">
<span class="spinner"></span>
処理中...
</span>
</button>
</form>
<div id="purchase-result"></div>
ここでは hx-trigger="click once" により、ボタンは 1 回しかクリックできません。さらに hx-disabled-elt="this" で、リクエスト中は物理的に無効化されます。
CSS 実装
css/* ボタンの基本スタイル */
button[type='submit'] {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
css/* リクエスト中のスタイル */
button[type='submit']:disabled {
background-color: #6c757d;
cursor: not-allowed;
opacity: 0.6;
}
/* インジケーターの表示制御 */
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
}
.htmx-request .button-text {
display: none;
}
css/* スピナーのアニメーション */
.spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
サーバー側の処理(Node.js + Express)
javascript// 購入 API のエンドポイント
import express from 'express';
import { body, validationResult } from 'express-validator';
const app = express();
javascript// 購入処理のハンドラー
app.post('/api/purchase',
// バリデーション
body('product_id').notEmpty().withMessage('商品IDは必須です'),
body('quantity').isInt({ min: 1 }).withMessage('数量は1以上の整数です'),
async (req, res) => {
// バリデーション結果の確認
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).send(`
<div class="error">
<p>エラーが発生しました</p>
<ul>
${errors.array().map(e => `<li>${e.msg}</li>`).join('')}
</ul>
</div>
`);
}
javascript try {
// データベースへの登録処理
const { product_id, quantity } = req.body;
const order = await db.createOrder({
productId: product_id,
quantity: parseInt(quantity),
userId: req.session.userId,
createdAt: new Date()
});
javascript // 成功レスポンスを返す
res.send(`
<div class="success">
<h4>購入が完了しました</h4>
<p>注文番号: ${order.id}</p>
<p>商品: ${order.productName}</p>
<p>金額: ¥${order.amount.toLocaleString()}</p>
</div>
`);
} catch (error) {
console.error('Purchase error:', error);
res.status(500).send(`
<div class="error">
<p>購入処理中にエラーが発生しました</p>
<p>しばらく時間をおいて再度お試しください</p>
</div>
`);
}
}
);
例 2: 検索フォームのオートコンプリート
検索フォームでは、ユーザーの入力に応じてリアルタイムに候補を表示する必要があります。この場合、debounce が有効です。
HTML 実装
html<!-- オートコンプリート検索フォーム -->
<div class="search-container">
<label for="search">商品検索</label>
<input
type="text"
id="search"
name="q"
placeholder="商品名を入力してください"
hx-get="/api/search"
hx-trigger="keyup changed debounce:300ms"
hx-target="#search-results"
hx-indicator="#search-spinner"
autocomplete="off"
/>
</div>
html <div id="search-spinner" class="htmx-indicator">
<span class="spinner-small"></span>
検索中...
</div>
<div id="search-results"></div>
</div>
hx-trigger="keyup changed debounce:300ms" により、以下の制御が実現されています。
- keyup: キーを離した時に発火
- changed: 前回と値が変わった時のみ発火(同じ値の連続入力は無視)
- debounce:300ms: 最後の入力から 300ms 後にリクエスト送信
CSS 実装
css/* 検索コンテナのスタイル */
.search-container {
position: relative;
max-width: 500px;
margin: 20px 0;
}
#search {
width: 100%;
padding: 10px 40px 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
css/* 検索中のインジケーター */
#search-spinner {
display: none;
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
color: #666;
}
#search-spinner.htmx-indicator {
display: flex;
align-items: center;
gap: 6px;
}
css/* 検索結果の表示 */
#search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-top: none;
border-radius: 0 0 4px 4px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
}
#search-results:empty {
display: none;
}
サーバー側の処理
javascript// 検索 API のエンドポイント
app.get('/api/search', async (req, res) => {
const query = req.query.q;
// 空文字列の場合は空の結果を返す
if (!query || query.trim().length === 0) {
return res.send('');
}
javascript try {
// データベースで検索
const results = await db.searchProducts({
keyword: query,
limit: 10
});
javascript// 結果が空の場合
if (results.length === 0) {
return res.send(`
<div class="no-results">
<p>「${query}」に一致する商品が見つかりませんでした</p>
</div>
`);
}
javascript // 検索結果を HTML として返す
const html = results.map(product => `
<div class="search-result-item">
<img src="${product.thumbnail}" alt="${product.name}" />
<div class="item-info">
<h4>${product.name}</h4>
<p class="price">¥${product.price.toLocaleString()}</p>
</div>
</div>
`).join('');
res.send(html);
} catch (error) {
console.error('Search error:', error);
res.status(500).send(`
<div class="error">検索中にエラーが発生しました</div>
`);
}
});
例 3: いいねボタンの連打防止
SNS のようないいねボタンでは、throttle を使って短時間の連打を防ぎつつ、ユーザーに即座にフィードバックを提供します。
HTML 実装
html<!-- いいねボタン -->
<div class="like-container">
<button
class="like-button"
hx-post="/api/posts/123/like"
hx-trigger="click throttle:1s"
hx-swap="outerHTML"
hx-disabled-elt="this"
>
<svg class="heart-icon" viewBox="0 0 24 24">
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
/>
</svg>
<span class="like-count">128</span>
</button>
</div>
hx-swap="outerHTML" により、サーバーから返された HTML でボタン全体が置き換わります。これにより、いいね数の更新とボタンの状態変更が同時に反映されます。
CSS 実装
css/* いいねボタンの基本スタイル */
.like-button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: transparent;
border: 1px solid #ddd;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s ease;
}
.like-button:hover {
background-color: #ffe6e6;
border-color: #ff4444;
}
css/* ハートアイコンのスタイル */
.heart-icon {
width: 20px;
height: 20px;
fill: none;
stroke: #666;
stroke-width: 2;
transition: all 0.2s ease;
}
/* いいね済みの状態 */
.like-button.liked .heart-icon {
fill: #ff4444;
stroke: #ff4444;
}
.like-button.liked {
border-color: #ff4444;
background-color: #ffe6e6;
}
css/* リクエスト中のスタイル */
.like-button:disabled {
opacity: 0.6;
cursor: wait;
}
.like-count {
font-weight: 600;
color: #333;
}
サーバー側の処理
javascript// いいね API のエンドポイント
app.post('/api/posts/:postId/like', async (req, res) => {
const { postId } = req.params;
const userId = req.session.userId;
if (!userId) {
return res.status(401).send(`
<div class="error">ログインが必要です</div>
`);
}
javascript try {
// いいね状態をトグル
const isLiked = await db.isPostLikedByUser(postId, userId);
if (isLiked) {
// いいね解除
await db.unlikePost(postId, userId);
} else {
// いいね追加
await db.likePost(postId, userId);
}
javascript// 更新後のいいね数を取得
const likeCount = await db.getPostLikeCount(postId);
const newIsLiked = !isLiked;
javascript // 更新されたボタンを返す
res.send(`
<button
class="like-button ${newIsLiked ? 'liked' : ''}"
hx-post="/api/posts/${postId}/like"
hx-trigger="click throttle:1s"
hx-swap="outerHTML"
hx-disabled-elt="this">
<svg class="heart-icon" viewBox="0 0 24 24">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
<span class="like-count">${likeCount}</span>
</button>
`);
} catch (error) {
console.error('Like error:', error);
res.status(500).send(`
<div class="error">エラーが発生しました</div>
`);
}
});
パターン比較表
実装パターンごとの特徴と適用シーンを表にまとめました。
| # | パターン | 主な用途 | メリット | デメリット | 適用例 |
|---|---|---|---|---|---|
| 1 | once | 1 回限りの実行 | 完全に二重送信を防げる | 再実行できない | 会員登録、決済 |
| 2 | throttle | 連打制限 | 一定間隔で実行可能 | 即座に反応しない場合がある | いいねボタン、投票 |
| 3 | debounce | 入力完了待機 | 不要なリクエストを削減 | 反応が遅れる | 検索、オートコンプリート |
| 4 | disabled | ボタン無効化 | 視覚的に明確 | JavaScript 無効時は機能しない | フォーム送信 |
| 5 | 組み合わせ | 多層防御 | 最も堅牢 | 実装が複雑 | 重要な操作全般 |
まとめ
htmx における二重送信問題は、フレームワークのデフォルト挙動を理解し、適切な属性を使うことで確実に防ぐことができます。
重要なポイント
htmx のデフォルト防止機能
htmx は同一要素からのリクエストを自動的にブロックしますが、これに依存せず明示的な制御を行うことが推奨されます。
trigger パターンの使い分け
- once: 絶対に 1 回しか実行してはいけない操作(決済、登録)
- throttle: 短時間の連打を防ぎたい操作(いいね、投票)
- debounce: 入力完了を待ちたい操作(検索、フィルター)
disable パターンの活用
hx-disabled-elt でボタンを物理的に無効化し、CSS でローディング状態を表現することで、ユーザーに明確なフィードバックを提供できます。
多層防御の実践
実務では trigger + disable + インジケーターの組み合わせにより、技術的な防御とユーザー体験の両立を実現しましょう。
実装チェックリスト
以下のチェックリストを使って、実装の抜け漏れを確認してください。
- リクエストの発火条件を
hx-triggerで明示的に定義した - 重要な操作には
onceまたはthrottleを設定した - リクエスト中は
hx-disabled-eltでボタンを無効化した - ユーザーにローディング状態を視覚的に示した(
hx-indicator、CSS) - サーバー側でもバリデーションとエラーハンドリングを実装した
- 実際にブラウザで連打テストを実施し、二重送信が起きないことを確認した
htmx の持つシンプルさと強力さを活かしながら、適切な二重送信対策を実装することで、ユーザーにとって安全で快適な Web アプリケーションを構築できます。本記事で紹介したパターンを、ぜひ実際のプロジェクトで活用してみてください。
関連リンク
articlehtmx × Laravel/PHP 導入手順:Blade パーシャルとルート設計の落とし穴回避
articlehtmx と Stimulus/Turbo の責務分担を実験で検証:同一要件 3 実装レビュー
articlehtmx 運用 SLO 設計:サーバ応答・スワップ完了・履歴遷移の 3 指標を可視化
articlehtmx で二重送信が起きる/起きない問題の完全対処:trigger と disable パターン
articlehtmx で管理画面 CRUD を 10 倍速に:一覧・検索・編集・バルク操作テンプレ
articlehtmx でページネーション最適化:履歴操作・スクロール保持・a11y 対応まで
articleNext.js を Bun で動かす開発環境:起動速度・互換性・落とし穴
articleObsidian Properties 速見表:型・表示名・テンプレ連携の実例カタログ
articleNuxt useHead/useSeoMeta 定番スニペット集:OGP/構造化データ/国際化メタ
articleMermaid で描ける図の種類カタログ:flowchart/class/state/journey/timeline ほか完全整理
articleMCP サーバーを活用した AI チャットボット構築:実用的な事例と実装
articleNginx 変数 100 選:$request_id/$upstream_status/$ssl_protocol ほか即戦力まとめ
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来