Reactでアプリケーションを構築する際、予期しないエラーに直面することがあります。特に、React Routerを使用して複雑なルーティングを管理する場合、エラーは思わぬ箇所で発生する可能性があります。このような状況で、アプリケーション全体がクラッシュするのを防ぎ、ユーザーに適切な情報を提示する仕組みが求められます。
本記事では、React Routerのエラー境界を活用して、エラーハンドリングを行う方法を徹底解説します。エラー境界の基本から実装方法、カスタムエラーページの作成、さらに実践的な応用例までを網羅し、エラー処理に関する課題を解決するための実用的な知識を提供します。
エラー境界の概要とReactでの役割
エラー境界は、Reactでアプリケーションのクラッシュを防ぎ、ユーザーに適切なフィードバックを提供するための重要な仕組みです。Reactでは、コンポーネントの中で発生するレンダリングエラーをキャッチし、アプリケーション全体のクラッシュを防ぐことが可能です。
エラー境界とは
エラー境界は、React 16以降で導入された特定のコンポーネントで、以下のエラーをキャッチします。
- 子コンポーネントのレンダリングエラー
- ライフサイクルメソッド中のエラー
- 子コンポーネント内のイベントハンドラ外でのエラー
これにより、アプリケーションの他の部分を正常に動作させつつ、エラー発生箇所に代替のUIを表示できます。
エラー境界のメリット
- 安定性の向上: 特定の部分で発生したエラーが他の部分に波及するのを防ぎます。
- ユーザー体験の改善: ユーザーに適切なエラー情報を提示し、次のアクションを促すことが可能です。
- デバッグの容易さ: 問題箇所を特定しやすくなり、開発効率が向上します。
エラー境界とReact Router
React Routerを使用したアプリケーションでは、ルーティングエラーが発生する可能性があります。例えば、存在しないページにアクセスした場合や、非同期データ取得中にエラーが発生した場合です。エラー境界を導入することで、これらの問題に対応し、アプリケーションの一貫性を保つことができます。
エラー境界は、Reactアプリケーションの品質向上に不可欠な要素であり、特にルーティングの複雑化が進む中で、その重要性が高まっています。
React Routerにおけるエラー処理の必要性
React Routerを利用したアプリケーションでは、エラー処理を適切に実装することがユーザー体験の向上とアプリケーションの安定性に直結します。以下に、React Routerにおけるエラー処理が重要な理由を説明します。
ルーティング中に発生しうるエラーの例
- 無効なURLへのアクセス
存在しないページ(404エラー)にアクセスした際、ユーザーは適切なフィードバックを得られないと混乱します。 - データ取得中のエラー
非同期データフェッチ中にサーバーエラーやネットワークエラーが発生すると、ページのレンダリングが中断されることがあります。 - 非同期コンポーネント読み込みの失敗
React.lazyやコード分割を利用している場合、モジュール読み込み時にエラーが発生する可能性があります。
エラー処理の重要性
- ユーザーの離脱防止
エラー発生時に適切なエラーメッセージや再試行オプションを提示することで、ユーザーの離脱を防げます。 - アプリケーションの信頼性向上
エラー発生時にもアプリケーション全体が正常に動作することで、信頼性を向上させられます。 - デバッグ効率の向上
エラー箇所が明確になるため、開発中の問題解決が迅速化します。
React Routerでのエラー処理の課題
React Routerでは、ルーティングの階層が深くなるほど、エラー処理の複雑さが増します。また、複数のルートで異なるエラー処理が必要な場合、一貫性を持たせることが困難です。
これらの課題に対処するためには、エラー境界を活用し、React Routerと統合したエラー処理を実装することが重要です。本記事では、この方法を具体的に解説していきます。
エラー境界の設定方法
React Routerでエラー境界を設定することで、アプリケーション全体がクラッシュするのを防ぎ、エラーページを適切に表示できます。以下では、エラー境界を設定する具体的な手順をコード例とともに解説します。
エラー境界の基本的な構造
エラー境界は通常、クラスコンポーネントを使用して作成します。componentDidCatch
ライフサイクルメソッドを実装することで、エラーをキャッチして代替UIを表示できます。
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error caught by ErrorBoundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>何か問題が発生しました。</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
React Routerとの統合
エラー境界をReact Routerに統合することで、特定のルートでエラーが発生した場合にカスタムエラーページを表示できます。
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import NotFoundPage from './NotFoundPage';
const App = () => {
return (
<Router>
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</ErrorBoundary>
</Router>
);
};
export default App;
特定のルートにエラー境界を設定する
ルートごとにエラー境界を設定する場合は、特定のコンポーネントをエラー境界でラップします。
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import HomePage from './HomePage';
import ProblematicPage from './ProblematicPage';
const SpecificRouteErrorHandling = () => {
return (
<ErrorBoundary>
<ProblematicPage />
</ErrorBoundary>
);
};
export default SpecificRouteErrorHandling;
実装のポイント
- getDerivedStateFromError: エラー発生時の状態を設定します。
- componentDidCatch: ログ記録や追加のエラーハンドリングを行います。
- 柔軟な統合: エラー境界を全体または特定のルートに設定することで、アプリケーションの要件に応じたエラーハンドリングを実現します。
これでReact Routerにエラー境界を設定し、アプリケーションのエラーハンドリングを強化する準備が整いました。次は、カスタムエラーページの作成について詳しく見ていきます。
カスタムエラーページの作成
エラー発生時にユーザー体験を損なわないためには、わかりやすく親しみやすいカスタムエラーページの作成が重要です。以下では、React Routerとエラー境界を組み合わせてカスタムエラーページを実装する手順を解説します。
カスタムエラーページのデザインと構造
まず、エラーページとして利用するシンプルなコンポーネントを作成します。
import React from 'react';
import { useNavigate } from 'react-router-dom';
const ErrorPage = () => {
const navigate = useNavigate();
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Oops! Something went wrong.</h1>
<p>We're sorry for the inconvenience. Please try again later.</p>
<button onClick={() => navigate('/')}>Go to Home</button>
</div>
);
};
export default ErrorPage;
要点
- ユーザーフレンドリーなメッセージ: エラー内容を簡潔に伝える。
- 操作可能なボタン: ホームページや特定の場所に簡単に戻れるようにする。
エラーページのルーティング
次に、カスタムエラーページをルーティングに組み込みます。Routes
でエラーが発生した際にエラーページを表示する設定を追加します。
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ErrorPage from './ErrorPage';
import ErrorBoundary from './ErrorBoundary';
const App = () => {
return (
<Router>
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</ErrorBoundary>
</Router>
);
};
export default App;
カスタムエラーページのスタイル設定
CSSまたはCSS-in-JSを使用してデザインを向上させます。以下は簡単なCSSの例です。
/* styles.css */
.error-page {
text-align: center;
margin-top: 50px;
font-family: Arial, sans-serif;
}
.error-page h1 {
font-size: 2.5rem;
color: #ff6f61;
}
.error-page p {
font-size: 1.2rem;
margin: 20px 0;
}
.error-page button {
padding: 10px 20px;
font-size: 1rem;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
}
.error-page button:hover {
background-color: #0056b3;
}
組み合わせたアプリケーション全体のコード例
以下は、カスタムエラーページを含むアプリ全体のコード構造の例です。
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ErrorBoundary from './ErrorBoundary';
import ErrorPage from './ErrorPage';
const App = () => {
return (
<Router>
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</ErrorBoundary>
</Router>
);
};
export default App;
ポイント
- エラーページはシンプルかつ明確にする。
- 適切なナビゲーションオプションを提供する。
- デザインにこだわることでユーザーの安心感を高める。
このようにして、カスタムエラーページを実装することで、エラー発生時もユーザー体験を損なわないアプリケーションを構築できます。
エラー境界とサードパーティライブラリの併用
React Routerのエラー境界は、サードパーティライブラリと組み合わせることで、さらに強力で柔軟なエラーハンドリングを実現できます。ここでは、いくつかの代表的なライブラリとエラー境界の統合方法を紹介します。
例1: Sentryを使ったエラーログ収集
Sentryは、アプリケーションのエラーを追跡し、詳細なレポートを提供するツールです。エラー境界にSentryを統合することで、発生したエラーを即座に記録し、分析できます。
# Sentryのインストール
npm install @sentry/react @sentry/browser
import React from 'react';
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
integrations: [new BrowserTracing()],
tracesSampleRate: 1.0,
});
const SentryErrorBoundary = ({ children }) => (
<Sentry.ErrorBoundary fallback={<h1>Something went wrong</h1>}>
{children}
</Sentry.ErrorBoundary>
);
export default SentryErrorBoundary;
React Routerとの統合:
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import SentryErrorBoundary from './SentryErrorBoundary';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ErrorPage from './ErrorPage';
const App = () => {
return (
<Router>
<SentryErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</SentryErrorBoundary>
</Router>
);
};
export default App;
例2: React-Queryを使用したデータフェッチエラーの処理
React-Queryは非同期データフェッチを簡単に管理できるライブラリです。エラー境界を活用して、React-Queryで発生したエラーに応じたUIを表示できます。
# React-Queryのインストール
npm install @tanstack/react-query
import React from 'react';
import { useQuery } from '@tanstack/react-query';
const fetchData = async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const DataComponent = () => {
const { data, error, isLoading } = useQuery(['data'], fetchData);
if (isLoading) return <p>Loading...</p>;
if (error) throw error;
return <div>{data.content}</div>;
};
React Routerとの統合:
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
import DataComponent from './DataComponent';
import HomePage from './HomePage';
import ErrorPage from './ErrorPage';
const App = () => {
return (
<Router>
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/data" element={<DataComponent />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</ErrorBoundary>
</Router>
);
};
export default App;
例3: Axiosを使ったHTTPエラーハンドリング
Axiosを使用してデータフェッチを行う場合も、エラー境界でエラーをキャッチし、適切なUIを表示できます。
# Axiosのインストール
npm install axios
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const AxiosDataComponent = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
axios
.get('/api/data')
.then(response => setData(response.data))
.catch(error => setError(error));
}, []);
if (error) throw error;
return <div>{data ? data.content : 'Loading...'}</div>;
};
エラー境界でキャッチ:
<ErrorBoundary>
<AxiosDataComponent />
</ErrorBoundary>
統合のポイント
- エラーログ収集の自動化: Sentryなどを活用して、エラー情報をリアルタイムで収集。
- 非同期処理との連携: React-QueryやAxiosなどのライブラリで発生したエラーをエラー境界に集約。
- 柔軟な設計: サードパーティライブラリを活用することで、拡張性と再利用性を向上。
これらの手法を組み合わせることで、エラー境界を強化し、より堅牢なReactアプリケーションを構築できます。
よくあるエラーとその解決方法
React Routerでエラー境界を設定する際、開発者が直面しやすいエラーのいくつかを解説し、それぞれの解決方法を示します。これにより、エラー境界をスムーズに活用できるようになります。
エラー1: エラー境界がエラーをキャッチしない
原因: エラー境界は、レンダリングやライフサイクルメソッド中のエラーのみをキャッチします。イベントハンドラや非同期処理のエラーはキャッチされません。
解決方法:
- 非同期エラーの場合:
try...catch
を使用して非同期処理のエラーを明示的にキャッチします。
const handleClick = async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('Fetch error');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error occurred:', error);
}
};
- イベントハンドラの場合: エラー処理を個別に記述します。
const handleButtonClick = () => {
try {
// エラーが発生する可能性のあるコード
} catch (error) {
console.error('Error in button click:', error);
}
};
エラー2: カスタムエラーページが表示されない
原因: ルーティング設定が正しくないか、エラーページが指定されていない可能性があります。
解決方法:
- ルーティング設定の確認: ワイルドカードパス(
*
)を使用して、未定義のルートをエラーページにリダイレクトします。
<Routes>
<Route path="*" element={<ErrorPage />} />
</Routes>
- エラー境界の設定確認: ルート全体をエラー境界でラップしているか確認します。
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</ErrorBoundary>
エラー3: 非同期データ取得時のエラーがキャッチされない
原因: Reactのエラー境界は、非同期処理のエラーを直接キャッチできません。
解決方法:
- 非同期処理をエラー境界で扱えるようにするには、状態管理を工夫する必要があります。
const DataComponent = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(response => {
if (!response.ok) {
throw new Error('Fetch failed');
}
return response.json();
})
.then(setData)
.catch(setError);
}, []);
if (error) throw error;
return <div>{data ? data.content : 'Loading...'}</div>;
};
エラー4: エラー情報が適切にログに記録されない
原因: componentDidCatch
やconsole.error
が正しく実装されていない場合があります。
解決方法:
- エラー情報を適切に記録するよう、
componentDidCatch
を実装します。
componentDidCatch(error, errorInfo) {
console.error('Caught an error:', error, errorInfo);
}
- SentryやLogRocketなどのツールを統合して詳細なエラー情報を収集します。
エラー5: UIの不具合によるエラー境界の誤作動
原因: コンポーネント間の状態管理や不整合が原因となる場合があります。
解決方法:
- 状態管理ツール(例: Redux、Context API)を使用して、エラーの発生源を特定します。
- コンポーネント分離を徹底し、影響範囲を限定します。
ポイント
- 正しい設計: エラー境界がカバーする範囲を明確にする。
- ログの活用: エラー内容を記録し、迅速に問題を特定。
- 非同期エラーの処理:
try...catch
やカスタムフックを使用して補完的なエラーハンドリングを実装。
これらの解決策を活用すれば、React Routerでのエラー処理を効率的に管理し、堅牢なアプリケーションを構築できます。
エラー境界のテストとデバッグ方法
エラー境界の適切な動作を確認し、潜在的な問題を早期に解決するためには、テストとデバッグが欠かせません。このセクションでは、Reactアプリケーションにおけるエラー境界のテスト手法とデバッグのベストプラクティスを解説します。
エラー境界のテスト方法
エラー境界が正しくエラーをキャッチし、適切なUIを表示することを確認するためのテストを行います。
1. ユニットテスト
ユニットテストを使用してエラー境界の動作を検証します。テストライブラリとして、JestとReact Testing Libraryを使用する例を示します。
# 必要なライブラリのインストール
npm install --save-dev jest @testing-library/react
import React from 'react';
import { render, screen } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
const ProblematicComponent = () => {
throw new Error('Test error');
};
test('renders fallback UI when an error is caught', () => {
render(
<ErrorBoundary>
<ProblematicComponent />
</ErrorBoundary>
);
expect(screen.getByText('何か問題が発生しました。')).toBeInTheDocument();
});
このテストでは、エラーが発生した場合に適切なフォールバックUIが表示されるかを確認しています。
2. 統合テスト
エラー境界とReact Routerを組み合わせた動作をテストします。
import React from 'react';
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
import ErrorPage from './ErrorPage';
const ProblematicComponent = () => {
throw new Error('Test error');
};
test('navigates to the error page on error', () => {
render(
<Router>
<ErrorBoundary>
<Routes>
<Route path="/" element={<ProblematicComponent />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</ErrorBoundary>
</Router>
);
expect(screen.getByText('何か問題が発生しました。')).toBeInTheDocument();
});
3. エンドツーエンドテスト
CypressやPlaywrightを使い、実際のブラウザ環境でエラー境界の動作を確認します。
describe('Error Boundary', () => {
it('should display custom error page on error', () => {
cy.visit('/');
cy.get('h1').contains('Oops! Something went wrong.');
});
});
エラー境界のデバッグ方法
エラー境界で発生した問題を効率的にデバッグする手法を紹介します。
1. コンソールログの活用
componentDidCatch
メソッド内で、エラー情報をログに出力します。
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error);
console.error('Error info:', errorInfo);
}
2. エラーログ収集ツールの導入
SentryやLogRocketを使用して、エラー情報を収集します。これにより、発生したエラーの詳細を把握できます。
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
integrations: [new Sentry.BrowserTracing()],
tracesSampleRate: 1.0,
});
3. React DevToolsの使用
React DevToolsを使い、エラーが発生したコンポーネントツリーを調査します。どのコンポーネントが問題を引き起こしたのか特定するのに役立ちます。
4. 非同期処理のデバッグ
非同期処理に関連するエラーの場合は、try...catch
ブロックを導入してエラーメッセージを確認します。また、非同期関数の実行ステータスをロギングすることで、エラーの原因を特定します。
実践的なテストとデバッグのポイント
- シナリオベースのテスト: ユーザーが遭遇する可能性のあるエラーを洗い出し、シナリオに基づいてテストを行います。
- ロギングの一元化: コンソールログやエラーログツールを活用して、エラー情報を集約します。
- 再現性の確認: 問題が発生した環境や手順を再現可能にすることで、迅速なデバッグを実現します。
これらの方法を駆使して、エラー境界のテストとデバッグを効率的に進めましょう。
実際のプロジェクトでの応用例
React Routerのエラー境界は、リアルなプロジェクトで活用することで、エラーハンドリングを効率化し、ユーザー体験を向上させることができます。このセクションでは、実際のプロジェクトでエラー境界を適用する具体例を紹介します。
応用例1: 多言語対応アプリケーションのエラー処理
多言語対応のReactアプリケーションでは、ユーザーの使用言語に基づいてエラーメッセージを切り替える機能が求められます。
import React from 'react';
import { useTranslation } from 'react-i18next';
const ErrorPage = () => {
const { t } = useTranslation();
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>{t('error.title')}</h1>
<p>{t('error.message')}</p>
<button onClick={() => window.location.reload()}>
{t('error.retry')}
</button>
</div>
);
};
export default ErrorPage;
この例では、react-i18next
を使用して、ユーザーの言語に応じたエラーメッセージを表示します。
応用例2: 管理画面での詳細なエラーレポート
管理画面を含むアプリケーションでは、開発者や管理者向けに詳細なエラー情報を提供することが重要です。以下は、エラーログを含むページを作成する例です。
import React, { useState } from 'react';
const AdminErrorPage = ({ error, errorInfo }) => {
const [showDetails, setShowDetails] = useState(false);
return (
<div style={{ padding: '20px', border: '1px solid #ddd' }}>
<h1>An error occurred</h1>
<p>{error.message}</p>
<button onClick={() => setShowDetails(!showDetails)}>
{showDetails ? 'Hide Details' : 'Show Details'}
</button>
{showDetails && (
<pre style={{ background: '#f9f9f9', padding: '10px' }}>
{errorInfo.componentStack}
</pre>
)}
</div>
);
};
export default AdminErrorPage;
エラー境界での利用例:
import React from 'react';
import AdminErrorPage from './AdminErrorPage';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({ error, errorInfo });
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<AdminErrorPage
error={this.state.error}
errorInfo={this.state.errorInfo}
/>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
応用例3: API通信エラーのハンドリング
非同期通信が重要なアプリケーションでは、APIエラーをキャッチして適切にユーザーに伝える必要があります。
const DataFetcher = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/data')
.then((response) => {
if (!response.ok) throw new Error('API error');
return response.json();
})
.then(setData)
.catch(setError);
}, []);
if (error) throw error;
return <div>{data ? data.content : 'Loading...'}</div>;
};
export default DataFetcher;
エラー境界との統合例:
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import DataFetcher from './DataFetcher';
const App = () => {
return (
<ErrorBoundary>
<DataFetcher />
</ErrorBoundary>
);
};
export default App;
応用例4: モバイルアプリケーションでのユーザーフレンドリーな通知
モバイル向けReactアプリケーションでは、エラーを通知するためにモーダルを使用します。
import React from 'react';
import Modal from 'react-modal';
const ErrorModal = ({ isOpen, error, onClose }) => (
<Modal isOpen={isOpen} onRequestClose={onClose}>
<h2>Oops! An error occurred</h2>
<p>{error.message}</p>
<button onClick={onClose}>Close</button>
</Modal>
);
export default ErrorModal;
このように、プロジェクトの要件に応じてエラー境界をカスタマイズすることで、ユーザー体験とアプリケーションの信頼性を向上させることができます。
まとめ
本記事では、React Routerでエラー境界を活用し、エラーハンドリングを改善する方法を解説しました。エラー境界の基本概念から、カスタムエラーページの作成、サードパーティライブラリとの統合、テストとデバッグ手法、そして実際のプロジェクトでの応用例までを網羅しました。
エラー境界を正しく実装することで、アプリケーションの信頼性とユーザー体験が大幅に向上します。特に、React Routerの複雑なルーティング構成を持つアプリケーションでは、エラー境界がプロジェクトの安定性を支える重要な要素となります。
適切なエラーハンドリングを備えたReactアプリケーションを構築し、予期せぬ問題が発生した際もスムーズなユーザー体験を提供しましょう。
コメント