Remix を選ぶ基準:認証・API・CMS 観点での要件適合チェック
Web アプリケーションのフレームワーク選定は、プロジェクトの成否を左右する重要な決断です。特に認証機能、API 連携、CMS との統合といった要素は、ビジネスロジックの中核を担うため、フレームワークがこれらをどのようにサポートするかを理解することが不可欠でしょう。
本記事では、Remix が認証・API・CMS の観点でどのような特性を持ち、どんなプロジェクトに適しているのかを詳しく解説します。これから Remix を導入しようと考えている方、あるいは他のフレームワークと比較検討している方にとって、実践的な判断材料となる内容をお届けしますね。
背景
Web フレームワークに求められる要素
現代の Web アプリケーション開発では、ユーザー認証、外部 API との連携、コンテンツ管理システム(CMS)との統合が標準的な要件となっています。これらの機能を効率的に実装できるかどうかが、開発速度とアプリケーションの品質を大きく左右するのです。
従来の SPA(Single Page Application)フレームワークでは、これらの機能をクライアントサイドで実装することが一般的でした。しかし、セキュリティ、SEO、初期表示速度などの課題から、サーバーサイドレンダリング(SSR)やハイブリッドアプローチが再評価されています。
Remix の登場と特徴
Remix は React をベースとした Web フレームワークで、Web 標準に忠実なアプローチを採用しているのが特徴です。特に以下の点で注目されています。
- サーバーサイドとクライアントサイドのシームレスな統合
- Web 標準の Request/Response API の活用
- プログレッシブエンハンスメントの実現
- ネストされたルーティングによる柔軟なデータロード
以下の図で、Remix のデータフロー構造を理解しましょう。
mermaidflowchart TB
browser["ブラウザ"]
remix["Remix アプリ"]
loader["Loader 関数<br/>(サーバー)"]
action["Action 関数<br/>(サーバー)"]
auth["認証サービス"]
api["外部 API"]
cms["Headless CMS"]
browser -->|"GET リクエスト"| remix
remix --> loader
loader --> auth
loader --> api
loader --> cms
loader -->|"データ返却"| remix
remix -->|"HTML + データ"| browser
browser -->|"POST/PUT/DELETE"| remix
remix --> action
action --> auth
action --> api
action -->|"レスポンス"| remix
remix -->|"リダイレクト/更新"| browser
この図から、Remix が Request/Response サイクルをどのように扱うかが見えてきます。Loader でデータを取得し、Action でデータ更新を処理する明確な分離が特徴的ですね。
課題
従来のフレームワークでの認証実装の問題点
多くの SPA フレームワークでは、認証状態の管理がクライアントサイドに偏りがちです。これにより以下の課題が発生します。
- セキュリティリスク: トークンをローカルストレージに保存することによる XSS 攻撃のリスク
- 初期表示の遅延: 認証状態の確認がクライアントサイドで行われるため、画面のちらつきが発生
- コード複雑化: サーバーとクライアントで認証ロジックを二重に管理する必要性
以下の表で、認証実装における課題を整理しました。
| # | 課題項目 | 従来の SPA | サーバー主導型 |
|---|---|---|---|
| 1 | トークン管理 | LocalStorage/SessionStorage | HTTP-only Cookie |
| 2 | 初期表示 | クライアントで認証確認 | サーバーで事前確認 |
| 3 | セキュリティ | XSS のリスクあり | CSRF 対策が中心 |
| 4 | SEO 対応 | 難しい | 容易 |
| 5 | コード重複 | クライアント/サーバー両方 | サーバー中心 |
API 連携における課題
外部 API との連携では、以下のような問題に直面することが多いでしょう。
- CORS の複雑さ: クライアントから直接 API を呼ぶ場合の CORS 設定の煩雑さ
- API キーの露出: クライアントサイドコードに API キーが含まれるリスク
- エラーハンドリング: ネットワークエラーや API エラーの統一的な処理が困難
- キャッシング戦略: データの鮮度とパフォーマンスのバランス調整
以下の図で、従来の SPA での API 連携における課題を可視化します。
mermaidflowchart LR
user["ユーザー"]
spa["SPA<br/>(クライアント)"]
api1["外部 API A"]
api2["外部 API B"]
api3["CMS API"]
user -->|"1. アクセス"| spa
spa -->|"2. API キー含む<br/>リクエスト"| api1
spa -->|"3. CORS 設定<br/>必要"| api2
spa -->|"4. 認証トークン<br/>露出リスク"| api3
style spa fill:#ffcccc
style api1 fill:#ffeecc
style api2 fill:#ffeecc
style api3 fill:#ffeecc
この図のポイントは、クライアントから直接 API を呼び出すことで発生するセキュリティリスクと設定の複雑さです。
CMS 連携の難しさ
Headless CMS を活用する際、以下の課題が顕在化します。
- データフェッチのタイミング: クライアントサイドでのフェッチによる表示遅延
- プレビュー機能: 下書きコンテンツのプレビュー実装の複雑さ
- インクリメンタル再生成: コンテンツ更新時の効率的な再ビルド
- 型安全性: CMS から取得するデータの型定義と検証
解決策
Remix による認証実装のアプローチ
Remix は、サーバーサイドでの認証処理を第一級の機能としてサポートしています。具体的には以下のような実装パターンが推奨されます。
セッション管理の基本
Remix では、サーバーサイドのセッション管理が標準機能として提供されています。
typescript// app/services/session.server.ts
import { createCookieSessionStorage } from '@remix-run/node';
// セッションストレージの作成
export const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true, // XSS 攻撃を防ぐ
maxAge: 60 * 60 * 24 * 7, // 7日間
path: '/',
sameSite: 'lax', // CSRF 対策
secrets: [process.env.SESSION_SECRET],
secure: process.env.NODE_ENV === 'production',
},
});
上記のコードでは、HTTP-only Cookie を使った安全なセッション管理を実現しています。httpOnly と sameSite 属性により、XSS と CSRF の両方に対応できるのです。
ユーザー認証の実装
認証状態の確認と保護されたルートの実装は、Loader 関数内で行います。
typescript// app/services/auth.server.ts
import { redirect } from '@remix-run/node';
import { sessionStorage } from './session.server';
// セッションからユーザー情報を取得
export async function getUserFromSession(request: Request) {
const session = await sessionStorage.getSession(
request.headers.get('Cookie')
);
const userId = session.get('userId');
if (!userId) return null;
// データベースからユーザー情報を取得
return await db.user.findUnique({
where: { id: userId },
});
}
typescript// 認証が必要なページの保護
export async function requireUser(request: Request) {
const user = await getUserFromSession(request);
if (!user) {
throw redirect('/login');
}
return user;
}
これらの関数を使うことで、認証状態の確認がサーバーサイドで完結し、セキュアな実装が可能になりますね。
ログイン処理の実装
Action 関数を使って、ログイン処理を実装します。
typescript// app/routes/login.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { sessionStorage } from "~/services/session.server";
// ログイン処理
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
// バリデーション
if (typeof email !== "string" || typeof password !== "string") {
return json(
{ error: "Invalid form data" },
{ status: 400 }
);
}
typescript // ユーザー認証
const user = await verifyLogin(email, password);
if (!user) {
return json(
{ error: "Invalid email or password" },
{ status: 401 }
);
}
// セッションの作成
const session = await sessionStorage.getSession();
session.set("userId", user.id);
// Cookie をセットしてリダイレクト
return redirect("/dashboard", {
headers: {
"Set-Cookie": await sessionStorage.commitSession(session),
},
});
}
この実装により、フォーム送信から認証、セッション作成、リダイレクトまでがサーバーサイドで完結します。
API 連携の実装パターン
Remix では、Loader と Action を使って API 連携を安全に実装できます。
Loader での API データ取得
外部 API からのデータ取得は、Loader 関数内で行うのが基本です。
typescript// app/routes/products.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
// API キーはサーバー環境変数から取得
const API_KEY = process.env.EXTERNAL_API_KEY;
export async function loader({ request }: LoaderFunctionArgs) {
// 外部 API からデータ取得
const response = await fetch("https://api.example.com/products", {
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
});
typescript if (!response.ok) {
// エラーハンドリング
throw new Response("Failed to fetch products", {
status: response.status,
statusText: response.statusText,
});
}
const products = await response.json();
return json({ products });
}
この実装により、API キーがクライアントに露出することなく、安全にデータを取得できるのです。
エラーハンドリングの統一
Remix のエラーバウンダリを使って、統一的なエラー処理を実現します。
typescript// app/routes/products.tsx
import { useRouteError, isRouteErrorResponse } from "@remix-run/react";
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div className="error-container">
<h1>Error {error.status}</h1>
<p>{error.statusText}</p>
<p>{error.data}</p>
</div>
);
}
typescript return (
<div className="error-container">
<h1>予期しないエラーが発生しました</h1>
<p>しばらく時間をおいて再度お試しください。</p>
</div>
);
}
エラーバウンダリにより、API エラーを適切にユーザーに伝えることができますね。
CMS 連携の実装戦略
Headless CMS との連携も、Loader を使うことで効率的に実装できます。
CMS クライアントの設定
まず、CMS クライアントをサーバーサイドで初期化します。
typescript// app/services/cms.server.ts
import { createClient } from '@contentful/contentful';
// Contentful クライアントの作成例
export const cmsClient = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});
typescript// プレビュー用クライアント
export const previewClient = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
host: 'preview.contentful.com',
});
環境変数から CMS の認証情報を取得することで、セキュアな接続を確保しています。
コンテンツの取得と型安全性
型定義を使って、CMS から取得するデータの型安全性を確保します。
typescript// app/types/cms.ts
export interface BlogPost {
id: string;
title: string;
slug: string;
content: string;
publishedAt: string;
author: {
name: string;
avatar: string;
};
}
typescript// app/routes/blog.$slug.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { cmsClient } from "~/services/cms.server";
import type { BlogPost } from "~/types/cms";
export async function loader({ params }: LoaderFunctionArgs) {
const { slug } = params;
// CMS からコンテンツ取得
const entry = await cmsClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
limit: 1,
});
typescript if (!entry.items.length) {
throw new Response("Not Found", { status: 404 });
}
const post = entry.items[0];
return json({
post: {
id: post.sys.id,
title: post.fields.title,
slug: post.fields.slug,
content: post.fields.content,
publishedAt: post.fields.publishedAt,
author: post.fields.author,
},
});
}
型定義により、開発時にコンテンツ構造の変更を早期に検出できるのです。
プレビュー機能の実装
下書きコンテンツのプレビューは、クエリパラメータで制御します。
typescript// app/routes/blog.$slug.tsx
export async function loader({
params,
request,
}: LoaderFunctionArgs) {
const url = new URL(request.url);
const isPreview =
url.searchParams.get('preview') === 'true';
// プレビューモードの判定
const client = isPreview ? previewClient : cmsClient;
const entry = await client.getEntries({
content_type: 'blogPost',
'fields.slug': params.slug,
limit: 1,
});
// ... 残りの処理
}
このように、プレビューと本番で異なる CMS クライアントを使い分けることで、柔軟なコンテンツ管理が実現できますね。
具体例
実践例:認証付き管理画面の構築
認証、API、CMS の全てを統合した実践的な例を見ていきましょう。
プロジェクト構成
textapp/
├── routes/
│ ├── _index.tsx # トップページ
│ ├── login.tsx # ログインページ
│ ├── dashboard.tsx # 管理画面(認証必須)
│ └── api.products.ts # API エンドポイント
├── services/
│ ├── auth.server.ts # 認証サービス
│ ├── session.server.ts # セッション管理
│ └── cms.server.ts # CMS クライアント
└── types/
└── index.ts # 型定義
この構成により、関心の分離が明確になり、保守性の高いコードベースが実現できます。
保護されたルートの実装
管理画面を認証で保護する実装例です。
typescript// app/routes/dashboard.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { requireUser } from "~/services/auth.server";
import { cmsClient } from "~/services/cms.server";
export async function loader({ request }: LoaderFunctionArgs) {
// 認証チェック(未認証の場合は /login にリダイレクト)
const user = await requireUser(request);
// CMS からダッシュボード用データ取得
const dashboardData = await cmsClient.getEntries({
content_type: "dashboardWidget",
});
typescript // 外部 API から統計データ取得
const statsResponse = await fetch("https://api.example.com/stats", {
headers: {
"Authorization": `Bearer ${process.env.API_KEY}`,
},
});
const stats = await statsResponse.json();
return json({
user,
widgets: dashboardData.items,
stats,
});
}
typescriptexport default function Dashboard() {
const { user, widgets, stats } =
useLoaderData<typeof loader>();
return (
<div className='dashboard'>
<h1>ようこそ、{user.name}さん</h1>
<div className='stats-grid'>
<div className='stat-card'>
<h3>総訪問者数</h3>
<p>{stats.totalVisitors}</p>
</div>
<div className='stat-card'>
<h3>今日の訪問者</h3>
<p>{stats.todayVisitors}</p>
</div>
</div>
<div className='widgets'>
{widgets.map((widget) => (
<div key={widget.sys.id}>
{/* ウィジェット表示 */}
</div>
))}
</div>
</div>
);
}
この実装では、認証チェック、CMS データ取得、API 連携が全てサーバーサイドで完結しています。
以下の図で、このダッシュボードのデータフローを視覚化します。
mermaidsequenceDiagram
participant U as ユーザー
participant R as Remix
participant A as Auth
participant C as CMS
participant API as 外部 API
U->>R: /dashboard にアクセス
R->>A: セッション確認
alt 未認証
A-->>R: null を返却
R-->>U: /login にリダイレクト
else 認証済み
A-->>R: ユーザー情報
R->>C: ダッシュボードデータ要求
C-->>R: ウィジェット情報
R->>API: 統計データ要求
API-->>R: 統計データ
R-->>U: HTML + データ表示
end
このシーケンス図のポイントは、認証チェックが最初に行われ、認証済みユーザーのみがデータ取得処理に進める点です。
実践例:記事管理システム
CMS と連携した記事管理システムの実装を見ていきましょう。
記事一覧ページ
typescript// app/routes/admin.posts._index.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData, Link } from "@remix-run/react";
import { requireUser } from "~/services/auth.server";
import { cmsClient } from "~/services/cms.server";
export async function loader({ request }: LoaderFunctionArgs) {
await requireUser(request);
// ページネーション情報の取得
const url = new URL(request.url);
const page = parseInt(url.searchParams.get("page") || "1");
const limit = 10;
const skip = (page - 1) * limit;
typescript // CMS から記事一覧を取得
const entries = await cmsClient.getEntries({
content_type: "blogPost",
limit,
skip,
order: "-sys.createdAt",
});
return json({
posts: entries.items,
total: entries.total,
page,
totalPages: Math.ceil(entries.total / limit),
});
}
typescriptexport default function AdminPosts() {
const { posts, page, totalPages } =
useLoaderData<typeof loader>();
return (
<div className='admin-posts'>
<h1>記事管理</h1>
<table>
<thead>
<tr>
<th>タイトル</th>
<th>ステータス</th>
<th>作成日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{posts.map((post) => (
<tr key={post.sys.id}>
<td>{post.fields.title}</td>
<td>
{post.sys.publishedAt ? '公開' : '下書き'}
</td>
<td>
{new Date(
post.sys.createdAt
).toLocaleDateString()}
</td>
<td>
<Link to={`/admin/posts/${post.sys.id}`}>
編集
</Link>
</td>
</tr>
))}
</tbody>
</table>
<div className='pagination'>
{Array.from(
{ length: totalPages },
(_, i) => i + 1
).map((p) => (
<Link
key={p}
to={`?page=${p}`}
className={p === page ? 'active' : ''}
>
{p}
</Link>
))}
</div>
</div>
);
}
ページネーション機能を含む記事一覧が、シンプルな実装で実現できていますね。
記事の作成・更新
Action 関数を使って、記事の作成と更新を処理します。
typescript// app/routes/admin.posts.new.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { requireUser } from "~/services/auth.server";
export async function action({ request }: ActionFunctionArgs) {
const user = await requireUser(request);
const formData = await request.formData();
const title = formData.get("title");
const content = formData.get("content");
const status = formData.get("status"); // "draft" or "published"
typescript// バリデーション
if (typeof title !== 'string' || title.length < 1) {
return json(
{ error: 'タイトルは必須です' },
{ status: 400 }
);
}
if (typeof content !== 'string' || content.length < 1) {
return json({ error: '本文は必須です' }, { status: 400 });
}
typescript // CMS に記事を作成
try {
const entry = await cmsClient.createEntry("blogPost", {
fields: {
title: { "ja": title },
content: { "ja": content },
author: {
"ja": {
sys: { type: "Link", linkType: "Entry", id: user.cmsId }
}
},
},
});
// 公開ステータスの場合は publish
if (status === "published") {
await entry.publish();
}
typescript return redirect(`/admin/posts/${entry.sys.id}`);
} catch (error) {
console.error("Failed to create post:", error);
return json(
{ error: "記事の作成に失敗しました" },
{ status: 500 }
);
}
}
この実装により、フォームから送信されたデータを CMS に保存し、適切にエラーハンドリングを行っています。
要件適合チェックリスト
以下の表で、Remix が各要件にどう対応しているかをまとめました。
| # | 要件カテゴリ | 機能 | Remix での実装方法 | 適合度 |
|---|---|---|---|---|
| 1 | 認証 | セッション管理 | Cookie Session Storage | ★★★★★ |
| 2 | 認証 | ルート保護 | Loader での認証チェック | ★★★★★ |
| 3 | 認証 | ログイン/ログアウト | Action 関数 | ★★★★★ |
| 4 | 認証 | OAuth 連携 | サードパーティライブラリ統合 | ★★★★☆ |
| 5 | API | REST API 連携 | Loader/Action での fetch | ★★★★★ |
| 6 | API | GraphQL 連携 | クライアント統合 | ★★★★☆ |
| 7 | API | エラーハンドリング | Error Boundary | ★★★★★ |
| 8 | API | キャッシング | HTTP Cache Headers | ★★★★☆ |
| 9 | CMS | コンテンツ取得 | Loader での CMS クライアント | ★★★★★ |
| 10 | CMS | プレビュー機能 | クエリパラメータ制御 | ★★★★★ |
| 11 | CMS | 型安全性 | TypeScript 型定義 | ★★★★☆ |
| 12 | CMS | インクリメンタル更新 | Revalidation 機能 | ★★★★☆ |
このチェックリストから、Remix が認証・API・CMS の各観点で高い適合度を持つことがわかりますね。
パフォーマンス最適化
Remix の特性を活かしたパフォーマンス最適化の実装例です。
キャッシュヘッダーの設定
typescript// app/routes/blog.$slug.tsx
export async function loader({
params,
}: LoaderFunctionArgs) {
const post = await cmsClient.getEntries({
content_type: 'blogPost',
'fields.slug': params.slug,
limit: 1,
});
// キャッシュヘッダーを設定
return json(
{ post: post.items[0] },
{
headers: {
'Cache-Control':
'public, max-age=300, s-maxage=3600',
},
}
);
}
この設定により、CDN レベルでのキャッシングが可能になり、CMS への負荷を軽減できるのです。
リソースルートによる API エンドポイント
Remix のリソースルートを使って、JSON API を提供できます。
typescript// app/routes/api.posts.ts
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { cmsClient } from "~/services/cms.server";
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const category = url.searchParams.get("category");
const entries = await cmsClient.getEntries({
content_type: "blogPost",
...(category && { "fields.category": category }),
limit: 20,
});
typescript return json(
{
posts: entries.items.map((item) => ({
id: item.sys.id,
title: item.fields.title,
slug: item.fields.slug,
excerpt: item.fields.excerpt,
})),
},
{
headers: {
"Cache-Control": "public, max-age=60",
"Access-Control-Allow-Origin": "*",
},
}
);
}
リソースルートにより、外部からアクセス可能な API エンドポイントを簡単に構築できますね。
まとめ
Remix は認証・API・CMS の各観点で、現代的な Web アプリケーション開発に必要な機能を高いレベルで提供しています。
Remix を選ぶべきケース
以下のような要件がある場合、Remix は優れた選択肢となるでしょう。
認証面での適合性
- セキュアなセッション管理が必要なプロジェクト
- サーバーサイドでの認証状態管理を重視する場合
- SEO とセキュリティを両立させたい場合
API 連携での適合性
- 外部 API との連携が多いアプリケーション
- API キーを安全に管理したい場合
- 統一的なエラーハンドリングを実現したい場合
CMS 連携での適合性
- Headless CMS をコンテンツソースとして利用する場合
- プレビュー機能が必要なプロジェクト
- コンテンツ主導の Web サイトやアプリケーション
導入時の注意点
一方で、以下のような場合は慎重な検討が必要です。
- リアルタイム性が非常に重要なアプリケーション(WebSocket 中心)
- クライアントサイドの複雑な状態管理が主体のアプリ
- 既存の SPA アーキテクチャからの移行コストを考慮する必要がある場合
Remix は Web 標準に忠実で、サーバーとクライアントの責務を明確に分離できるフレームワークです。認証・API・CMS という重要な要素を、シンプルかつセキュアに実装できる点が最大の魅力といえるでしょう。
プロジェクトの要件を本記事のチェックリストと照らし合わせることで、Remix が適切な選択かどうかを判断する材料になれば幸いです。ぜひ、実際のプロジェクトで Remix の強みを体感してみてください。
関連リンク
articleRemix を選ぶ基準:認証・API・CMS 観点での要件適合チェック
articleRemix の仕組みを図解で理解:ルーティング/データロード/アクションの全体像
articleRemix で「Hydration failed」を解決:サーバ/クライアント不整合の診断手順
articleRemix 本番運用チェックリスト:ビルド・監視・バックアップ・脆弱性対応
articleRemix で管理画面テンプレ:表・フィルタ・CSV エクスポートの鉄板構成
articleRemix でブログをゼロから構築:Markdown・検索・タグ・OGP まで実装
articleTauri vs Electron vs Flutter デスクトップ:UX・DX・配布のしやすさ徹底比較
articleRuby と Python を徹底比較:スクリプト・Web・データ処理での得意分野
articleshadcn/ui のバンドルサイズ影響を検証:Tree Shaking・Code Split の実測データ
articleRedis Docker Compose 構築:永続化・監視・TLS まで 1 ファイルで
articleRemix を選ぶ基準:認証・API・CMS 観点での要件適合チェック
articleReact で管理画面を最短構築:テーブル・フィルタ・権限制御の実例
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 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来