best-practices について
セキュリティ、ブラウザー互換性、コード品質を確認する開発者向けWeb標準スキルです。ページ表示に基づくLighthouseの証拠と、依存関係、ヘッダー、設定、ソースの調査を組み合わせます。
主な機能
- 01証拠に基づくLighthouse監査手順
- 02セキュリティとヘッダーの確認
- 03ブラウザー互換性のガイダンス
- 04非推奨APIの確認
- 05コード品質とエラー処理の確認
活用シーン
- 01デプロイ前のサイトレビュー
- 02ブラウザーのコンソールやネットワーク障害の調査
- 03セキュリティヘッダーと依存関係の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ addyosmani/ web-quality-skills --skill best-practicesインストール後、Claude Code で/best-practicesを入力して読み込まれたか確認します。
同梱ファイルreferences/SECURITY.md
詳細
ベストプラクティス
Lighthouse のベストプラクティス監査に基づく、最新のウェブ開発標準です。セキュリティ、ブラウザー互換性、コード品質のパターンを扱います。
根拠に基づく監査ワークフロー
レンダリング済みのページを利用できる場合:
- その機能を利用できる場合は、Lighthouse のベストプラクティス監査をライブで実行します。Chrome DevTools MCP を使用する場合は、
lighthouse_auditを使用します。通常のページ読み込みにはナビゲーションモードを、現在の状態を保持する必要がある場合はスナップショットモードを使用します。 - 一覧に表示されたコンソールとネットワークの失敗を調べ、調査結果の裏付けになる場合に限って個別の詳細を取得します。
- 実行時の根拠を、依存関係、ヘッダー、設定、ソースの調査で補足します。Lighthouse は完全なセキュリティ評価ではありません。
- 該当するコードを修正し、同じ監査を再実行します。セキュリティ上の指摘とスタイル上の好みは分けて扱います。
ライブツールを利用できない場合は、Lighthouse CLI と、対象を絞った依存関係およびヘッダーの確認を使用します。Lighthouse のスコアが高いことを、アプリケーションが安全である証拠として決して報告しないでください。
セキュリティ
セキュリティが対象となる場合、またはライブ監査で関連する失敗が見つかった場合は、セキュリティリファレンスを参照してください。HTTPS/HSTS、CSP と Trusted Types、サブリソース完全性、ヘッダー、依存関係、サニタイズ、Cookie を扱います。
最低限、次の点を守ってください。
- 混在コンテンツを含めずに HTTPS を使用します。 関連するすべてのサブドメインが HTTPS に対応していることを確認してから、HSTS を追加します。
- 厳格な CSP は多層防御として扱います。 nonce またはハッシュを優先し、強制適用の前にレポートのみのモードでテストします。
- 信頼できない HTML をサニタイズし、DOM XSS のシンクを保護します。 マークアップが不要な場合は、テキスト API を優先します。
- サードパーティーコードのバージョンを固定し、レビューします。 配信方式が対応している場合は SRI を使用し、依存関係にパッチを適用した状態に保ちます。
- 実行時にレスポンスヘッダーを確認します。 ソースの設定だけでは、デプロイ済みのページが実際に送信する内容は証明できません。
ブラウザー互換性
Doctype 宣言
<HTML>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<!DOCTYPE html>
<html lang="en">
文字エンコーディング
<html>
<head>
<title>Page</title>
<meta charset="UTF-8">
</head>
<html>
<head>
<meta charset="UTF-8">
<title>Page</title>
</head>
Viewport メタタグ
<head>
<title>Page</title>
</head>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page</title>
</head>
機能検出
// ❌ Browser detection (brittle)
if (navigator.userAgent.includes('Chrome')) {
// Chrome-specific code
}
// ✅ Feature detection
if ('IntersectionObserver' in window) {
// Use IntersectionObserver
} else {
// Fallback
}
// ✅ Using @supports in CSS
@supports (display: grid) {
.container {
display: grid;
}
}
@supports not (display: grid) {
.container {
display: flex;
}
}
ポリフィル(必要な場合)
サポート対象ブラウザーの一覧に合わせて、ビルド時にポリフィルをバンドルする(Babel/SWC + core-js、または @vitejs/plugin-legacy)ことを優先してください。これにより実行時のチェックが不要になり、最新のブラウザーにポリフィルのバイト列を送信せずに済みます。
実行時にポリフィルを読み込む必要がある場合は、スクリプト要素を追加してください。document.write は絶対に使用しないでください(パーサーをブロックし、非同期/遅延コンテキストでは動作しません)。
<script>
if (!('fetch' in window)) {
const s = document.createElement('script');
s.src = '/polyfills/fetch.js';
s.defer = true;
document.head.appendChild(s);
}
</script>
自分で管理していないサードパーティー CDN からポリフィルを読み込まないでください。 polyfill.io サービスは、サプライチェーン攻撃により2024 年半ばに侵害され、約 10 万のサイトにマルウェアを配信するために使われました。セルフホストするか、審査済みのミラー(例:Cloudflare の cdnjs ポリフィルビルド)を使用し、サブリソース完全性でバージョンを固定してください。
非推奨 API
これらは避ける
// ❌ document.write (blocks parsing)
document.write('<script src="..."></script>');
// ✅ Dynamic script loading
const script = document.createElement('script');
script.src = '...';
document.head.appendChild(script);
// ❌ Synchronous XHR (blocks main thread)
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // false = synchronous
// ✅ Async fetch
const response = await fetch(url);
// ❌ Application Cache (deprecated)
<html manifest="cache.manifest">
// ✅ Service Workers
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
イベントリスナーの passive オプション
// ❌ Non-passive touch/wheel (may block scrolling)
element.addEventListener('touchstart', handler);
element.addEventListener('wheel', handler);
// ✅ Passive listeners (allows smooth scrolling)
element.addEventListener('touchstart', handler, { passive: true });
element.addEventListener('wheel', handler, { passive: true });
// ✅ If you need preventDefault, be explicit
element.addEventListener('touchstart', handler, { passive: false });
コンソールとエラー
コンソールエラーをなくす
// ❌ Errors in production
console.log('Debug info'); // Remove in production
throw new Error('Unhandled'); // Catch all errors
// ✅ Proper error handling
try {
riskyOperation();
} catch (error) {
// Log to error tracking service
errorTracker.captureException(error);
// Show user-friendly message
showErrorMessage('Something went wrong. Please try again.');
}
エラーバウンダリー(React)
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
errorTracker.captureException(error, { extra: info });
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<App />
</ErrorBoundary>
グローバルエラーハンドラー
// Catch unhandled errors
window.addEventListener('error', (event) => {
errorTracker.captureException(event.error);
});
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
errorTracker.captureException(event.reason);
});
ソースマップ
本番環境の設定
// ❌ Source maps exposed in production
// webpack.config.js
module.exports = {
devtool: 'source-map', // Exposes source code
};
// ✅ Hidden source maps (uploaded to error tracker)
module.exports = {
devtool: 'hidden-source-map',
};
// ✅ Or no source maps in production
module.exports = {
devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
};
エラートラッカーにアップロードする際は、本番用マップから sourcesContent を削除してください。デフォルトでは、バンドラーが元のソース全体を .map ファイルに埋め込みます。マップを入手した人は(設定を誤ったアップロード手順などを通じて入手した場合も)、圧縮されていないコードを取得できます。バンドラーで sourcesContent を除外するよう設定するか、アップロード時に除外する Sentry/Bugsnag CLI フラグを使用してください。
Vite では、//# sourceMappingURL= コメントがバンドルに出力されないよう、'true' より sourcemap: 'hidden' を優先してください。
パフォーマンスのベストプラクティス
ブロッキングパターンを避ける
// ❌ Blocking script
<script src="heavy-library.js"></script>
// ✅ Deferred script
<script defer src="heavy-library.js"></script>
// ❌ Blocking CSS import
@import url('other-styles.css');
// ✅ Link tags (parallel loading)
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="other-styles.css">
効率的なイベントハンドラー
// ❌ Handler on every element
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ Event delegation
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
メモリ管理
// ❌ Memory leak (never removed)
const handler = () => { /* ... */ };
window.addEventListener('resize', handler);
// ✅ Cleanup when done
const handler = () => { /* ... */ };
window.addEventListener('resize', handler);
// Later, when component unmounts:
window.removeEventListener('resize', handler);
// ✅ Using AbortController
const controller = new AbortController();
window.addEventListener('resize', handler, { signal: controller.signal });
// Cleanup:
controller.abort();
コード品質
有効な HTML
<div id="header">
<div id="header">
<ul>
<div>Item</div>
</ul>
<a href="/"><button>Click</button></a>
<header id="site-header">
</header>
<ul>
<li>Item</li>
</ul>
<a href="/" class="button">Click</a>
セマンティック HTML
<div class="header">
<div class="nav">
<div class="nav-item">Home</div>
</div>
</div>
<div class="main">
<div class="article">
<div class="title">Headline</div>
</div>
</div>
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
<article>
<h1>Headline</h1>
</article>
</main>
画像のアスペクト比
<img src="photo.jpg" width="300" height="100">
<img src="photo.jpg" width="300" height="225">
<img src="photo.jpg" style="width: 300px; height: 200px; object-fit: cover;">
権限とプライバシー
権限を適切にリクエストする
// ❌ Request on page load (bad UX, often denied)
navigator.geolocation.getCurrentPosition(success, error);
// ✅ Request in context, after user action
findNearbyButton.addEventListener('click', async () => {
// Explain why you need it
if (await showPermissionExplanation()) {
navigator.geolocation.getCurrentPosition(success, error);
}
});
権限ポリシー
<meta http-equiv="Permissions-Policy"
content="geolocation=(), camera=(), microphone=()">
<meta http-equiv="Permissions-Policy"
content="geolocation=(self 'https://maps.example.com')">
監査チェックリスト
セキュリティ(重要)
- HTTPS が有効で、混在コンテンツがない
- 脆弱な依存関係がない(
npm audit) - CSP ヘッダーが構成されている(
frame-ancestors、base-uri、form-actionを含む) -
require-trusted-types-for 'script'が適用されている(または展開中はレポート専用) - サードパーティの
<script>/<link rel="stylesheet">が SRI ハッシュで固定されている - セキュリティヘッダーが存在する(HSTS、X-Content-Type-Options、Referrer-Policy)
- ソースマップが公開されていない(アップロードされたものからは
sourcesContentを除去済み)
互換性
- 有効な HTML5 doctype
- head 内で最初に文字コードを宣言
- viewport meta タグが存在する
- 非推奨の API を使用していない
- scroll/touch に passive イベントリスナーを使用
コード品質
- コンソールエラーがない
- 有効な HTML(重複した ID がない)
- セマンティックな HTML 要素を使用
- 適切なエラーハンドリング
- コンポーネント内のメモリクリーンアップ
UX
- 邪魔なインタースティシャルがない
- 権限リクエストは文脈に沿って行う
- 明確なエラーメッセージ
- 適切な画像のアスペクト比
ツール
| ツール | 目的 |
|---|---|
npm audit | 依存関係の脆弱性 |
| SecurityHeaders.com | ヘッダー分析 |
| W3C Validator | HTML 検証 |
ライブ Lighthouse 監査(Chrome DevTools MCP: lighthouse_audit) | エージェント向けのレンダリング済みベストプラクティスチェック |
| Lighthouse CLI | ベストプラクティス監査のフォールバック |
| Observatory | セキュリティスキャン |