best-practices
セキュリティ・互換性・コード品質に関するモダンなWeb開発のベストプラクティスを適用します。「ベストプラクティスを適用して」「セキュリティ監査」「コードのモダナイズ」「コード品質レビュー」「脆弱性チェック」といった依頼に対して使用します。
description の原文を見る
Apply modern web development best practices for security, compatibility, and code quality. Use when asked to "apply best practices", "security audit", "modernize code", "code quality review", or "check for vulnerabilities".
SKILL.md 本文
ベストプラクティス
Lighthouse ベストプラクティス監査に基づいた現代的な Web 開発標準。セキュリティ、ブラウザ互換性、コード品質パターンをカバーします。
セキュリティ
HTTPS の徹底
HTTPS の強制:
<!-- ❌ 混合コンテンツ -->
<img src="http://example.com/image.jpg">
<script src="http://cdn.example.com/script.js"></script>
<!-- ✅ HTTPS のみ -->
<img src="https://example.com/image.jpg">
<script src="https://cdn.example.com/script.js"></script>
プロトコル相対 URL (//example.com/...) は避けてください。HTTP 時代のパターンで、HTTPS のみのサイトでは利点がなく、実際のスキームをレビュアーに隠します。
HSTS ヘッダー:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content Security Policy (CSP)
<!-- Meta タグによる基本的な CSP -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;">
<!-- より良い: HTTP ヘッダー -->
CSP ヘッダー (推奨):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc123' https://trusted.com;
style-src 'self' 'nonce-abc123';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'self';
base-uri 'self';
form-action 'self';
インラインスクリプトに nonce を使用:
<script nonce="abc123">
// このインラインスクリプトは許可されます
</script>
Trusted Types (最新の DOM-XSS 対策)
厳密な CSP は信頼されていないスクリプトファイルの読み込みをブロックしますが、文字列が innerHTML、eval などの DOM-XSS シンクに到達するのを防ぎません。Trusted Types は 2026 年初頭から全主要ブラウザでベースライン対応しており、シンクに生の文字列を拒否させ、名前付きポリシーによって生成された型付きオブジェクトのみを受け入れるようにすることで、この穴を塞ぎます。
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default;
// サニタイズを行う単一のセントラルポリシー
const escape = trustedTypes.createPolicy('default', {
createHTML: (s) => DOMPurify.sanitize(s, { RETURN_TRUSTED_TYPE: true })
});
// ❌ これは強制下では TypeError をスローします
element.innerHTML = userInput;
// ✅ ポリシーを通じて実行されます
element.innerHTML = escape.createHTML(userInput);
Content-Security-Policy-Report-Only でロールアウトして、アプリ内のすべてのシンク使用法を検出してから、強制に切り替えます。Angular には Trusted Types の組み込みサポートがあります。React 19+ は Trusted Types が強制される場合に TrustedHTML を生成します。その他の場合は、DOMPurify がデファクトスタンダードのサニタイザーです。
Subresource Integrity (SRI) for third-party scripts
コントロールしていない CDN から読み込むすべての <script> と <link rel="stylesheet"> をピンします。CDN が侵害された場合 (2024 年の polyfill.io のように)、ブラウザはハッシュが一致しないファイルの実行を拒否します。
<script src="https://cdn.example.com/lib@1.2.3/dist/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
integrity はスペース区切りのハッシュを受け入れます。次のバージョンのハッシュをローテーション前に含めてダウンタイムを回避します。openssl dgst -sha384 -binary file.js | openssl base64 -A で生成します。SRI には crossorigin と CDN からの Access-Control-Allow-Origin レスポンスヘッダーが必要です。
セキュリティヘッダー
# クリックジャッキングを防止 — CSP の `frame-ancestors` (上記) を優先します。
# X-Frame-Options は古いブラウザのレガシーフォールバックです。
X-Frame-Options: DENY
# MIME タイプスニッフィングを防止
X-Content-Type-Options: nosniff
# X-XSS-Protection を送信しないでください。レガシーブラウザの XSS 監査機能は
# 廃止され、削除されました (Chrome 78、Edge 17)。場合によっては独自の脆弱性を
# 導入しました。代わりに厳密な CSP + Trusted Types (下記) を使用します。
# Referrer 情報を制御
Referrer-Policy: strict-origin-when-cross-origin
# Permissions policy (旧 Feature-Policy)
Permissions-Policy: geolocation=(), microphone=(), camera=()
脆弱なライブラリがない
# 脆弱性をチェック
npm audit
yarn audit
# 可能な場合は自動修正
npm audit fix
# 特定のパッケージをチェック
npm ls lodash
依存関係を最新に保つ:
// package.json
{
"scripts": {
"audit": "npm audit --audit-level=moderate",
"update": "npm update && npm audit fix"
}
}
避けるべき既知の脆弱なパターン:
// ❌ 信頼されていない入力の再帰的マージは __proto__、constructor、
// prototype キーを通じて Object.prototype を汚染する可能性があります。
_.merge(target, userInput); // lodash <4.17.20
$.extend(true, {}, target, userInput); // jQuery deep extend
Object.assign(target, ...userInputs); // それ自体は安全 (浅い)、しかしターゲットが
// Object.prototype 派生で userInput が
// __proto__ を含む場合は安全ではない
// ✅ 信頼されていないオブジェクトには null プロトタイプを使用して
// __proto__ は単なるキーになります
const safe = Object.create(null);
Object.assign(safe, userInput); // 浅い、再帰なし → 構造的に安全
// ✅ ディープコピーには structuredClone は __proto__ と関数をドロップします
const deepSafe = structuredClone(userInput);
// ✅ ディープマージには危険なキーを明示的にブロックするライブラリを使用します
// (例: lodash ≥4.17.21 _.mergeWith with a customizer、または deepmerge-ts)。
入力サニタイゼーション
// ❌ XSS 脆弱性
element.innerHTML = userInput;
document.write(userInput);
// ✅ 安全なテキストコンテンツ
element.textContent = userInput;
// ✅ HTML が必要な場合はサニタイズ
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
セキュアなクッキー
// ❌ 安全でないクッキー
document.cookie = "session=abc123";
// ✅ セキュアなクッキー (サーバーサイド)
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/
ブラウザ互換性
DOCTYPE 宣言
<!-- ❌ DOCTYPE がない、または無効 -->
<HTML>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<!-- ✅ HTML5 DOCTYPE -->
<!DOCTYPE html>
<html lang="en">
文字エンコーディング
<!-- ❌ charset がない、または遅い -->
<html>
<head>
<title>Page</title>
<meta charset="UTF-8">
</head>
<!-- ✅ Charset は head の最初の要素 -->
<html>
<head>
<meta charset="UTF-8">
<title>Page</title>
</head>
Viewport メタタグ
<!-- ❌ Viewport がない -->
<head>
<title>Page</title>
</head>
<!-- ✅ レスポンシブ viewport -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page</title>
</head>
機能検出
// ❌ ブラウザ検出 (脆弱)
if (navigator.userAgent.includes('Chrome')) {
// Chrome 固有のコード
}
// ✅ 機能検出
if ('IntersectionObserver' in window) {
// IntersectionObserver を使用
} else {
// フォールバック
}
// ✅ CSS で @supports を使用
@supports (display: grid) {
.container {
display: grid;
}
}
@supports not (display: grid) {
.container {
display: flex;
}
}
Polyfills (必要な場合)
ビルド時に polyfills をバンドル (Babel/SWC + core-js、または @vitejs/plugin-legacy) してサポート対象ブラウザリストでターゲット化することを優先します。これにより、ランタイム チェックが完全に排除され、最新のブラウザへの polyfill バイトの配送が回避されます。
ランタイムで polyfill を読み込む必要がある場合は、スクリプト要素を追加します — 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 を読み込まないでください。 polyfill.io サービスは 2024 年中盤にサプライチェーン攻撃で侵害され、約 100k サイトにマルウェアを配信するために使用されました。自ホストするか、検証済みミラーを使用してください (例: Cloudflare の cdnjs polyfill ビルド) — そして Subresource Integrity でバージョンをピンしてください。
廃止予定の API
これらを避ける
// ❌ document.write (パースをブロック)
document.write('<script src="..."></script>');
// ✅ 動的スクリプト読み込み
const script = document.createElement('script');
script.src = '...';
document.head.appendChild(script);
// ❌ 同期 XHR (メインスレッドをブロック)
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // false = 同期
// ✅ 非同期 fetch
const response = await fetch(url);
// ❌ Application Cache (廃止予定)
<html manifest="cache.manifest">
// ✅ Service Workers
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
イベントリスナーの passive オプション
// ❌ Passive でないタッチ/ホイール (スクロールをブロックする可能性)
element.addEventListener('touchstart', handler);
element.addEventListener('wheel', handler);
// ✅ Passive リスナー (滑らかなスクロールを許可)
element.addEventListener('touchstart', handler, { passive: true });
element.addEventListener('wheel', handler, { passive: true });
// ✅ preventDefault が必要な場合は明示的に
element.addEventListener('touchstart', handler, { passive: false });
コンソール & エラー
コンソールエラーがない
// ❌ 本番環境でのエラー
console.log('Debug info'); // 本番環境では削除
throw new Error('Unhandled'); // すべてのエラーをキャッチ
// ✅ 適切なエラーハンドリング
try {
riskyOperation();
} catch (error) {
// エラートラッキングサービスにログ
errorTracker.captureException(error);
// ユーザーフレンドリーなメッセージを表示
showErrorMessage('Something went wrong. Please try again.');
}
Error Boundaries (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;
}
}
// 使用例
<ErrorBoundary>
<App />
</ErrorBoundary>
グローバルエラーハンドラー
// 処理されないエラーをキャッチ
window.addEventListener('error', (event) => {
errorTracker.captureException(event.error);
});
// 処理されない Promise 拒否をキャッチ
window.addEventListener('unhandledrejection', (event) => {
errorTracker.captureException(event.reason);
});
ソースマップ
本番環境設定
// ❌ ソースマップが本番環境に公開
// webpack.config.js
module.exports = {
devtool: 'source-map', // ソースコードを公開
};
// ✅ 隠しソースマップ (エラートラッカーにアップロード)
module.exports = {
devtool: 'hidden-source-map',
};
// ✅ または本番環境ではソースマップなし
module.exports = {
devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
};
本番環境マップから sourcesContent をストリップ してエラートラッカーにアップロードするときは。デフォルトでは、バンドラーは完全なオリジナルソースを .map ファイル内に埋め込みます — マップを取得したすべての人 (設定ミスのアップロードステップ経由を含む) があなたのミニ化されていないコードを取得します。バンドラーを設定して sourcesContent を省略するか、アップロード時にそうする Sentry/Bugsnag CLI フラグを使用します。
Vite の場合、sourcemap: 'true' の代わりに 'hidden' を優先して、//# sourceMappingURL= コメントがバンドルに出力されないようにします。
パフォーマンスベストプラクティス
ブロッキングパターンを避ける
// ❌ ブロッキングスクリプト
<script src="heavy-library.js"></script>
// ✅ 遅延スクリプト
<script defer src="heavy-library.js"></script>
// ❌ ブロッキング CSS インポート
@import url('other-styles.css');
// ✅ Link タグ (並列読み込み)
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="other-styles.css">
効率的なイベントハンドラー
// ❌ 各要素にハンドラー
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ イベント委譲
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
メモリ管理
// ❌ メモリリーク (削除されない)
const handler = () => { /* ... */ };
window.addEventListener('resize', handler);
// ✅ 完了時にクリーンアップ
const handler = () => { /* ... */ };
window.addEventListener('resize', handler);
// 後で、コンポーネントがアンマウントされるとき:
window.removeEventListener('resize', handler);
// ✅ AbortController を使用
const controller = new AbortController();
window.addEventListener('resize', handler, { signal: controller.signal });
// クリーンアップ:
controller.abort();
コード品質
有効な HTML
<!-- ❌ 無効な HTML -->
<div id="header">
<div id="header"> <!-- 重複した ID -->
<ul>
<div>Item</div> <!-- 無効な子要素 -->
</ul>
<a href="/"><button>Click</button></a> <!-- 無効なネスト -->
<!-- ✅ 有効な HTML -->
<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>
<!-- ✅ セマンティック HTML5 -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
<article>
<h1>Headline</h1>
</article>
</main>
画像のアスペクト比
<!-- ❌ 歪んだ画像 -->
<img src="photo.jpg" width="300" height="100">
<!-- 実際の比率が 4:3 の場合、この画像は圧縮されます -->
<!-- ✅ アスペクト比を保持 -->
<img src="photo.jpg" width="300" height="225">
<!-- 実際の 4:3 寸法 -->
<!-- ✅ 柔軟性のために CSS object-fit を使用 -->
<img src="photo.jpg" style="width: 300px; height: 200px; object-fit: cover;">
パーミッション & プライバシー
パーミッションリクエストを適切に実行
// ❌ ページ読み込み時にリクエスト (悪い UX、多くの場合は拒否される)
navigator.geolocation.getCurrentPosition(success, error);
// ✅ コンテキスト内で、ユーザーアクション後にリクエスト
findNearbyButton.addEventListener('click', async () => {
// なぜ必要かを説明
if (await showPermissionExplanation()) {
navigator.geolocation.getCurrentPosition(success, error);
}
});
Permissions policy
<!-- 強力な機能を制限 -->
<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
- Charset が head の最初に宣言されている
- Viewport メタタグが存在している
- 廃止予定の API が使用されていない
- スクロール/タッチに対して Passive イベントリスナーが使用されている
コード品質
- コンソールエラーがない
- 有効な HTML (重複した ID がない)
- セマンティック HTML 要素が使用されている
- 適切なエラーハンドリング
- コンポーネントでのメモリクリーンアップ
UX
- 煩わしいインタースティシャルがない
- パーミッションリクエストがコンテキスト内
- エラーメッセージが明確
- 適切な画像アスペクト比
ツール
| ツール | 目的 |
|---|---|
npm audit | 依存関係の脆弱性 |
| SecurityHeaders.com | ヘッダー分析 |
| W3C Validator | HTML 検証 |
| Lighthouse | ベストプラクティス監査 |
| Observatory | セキュリティスキャン |
参考資料
- MDN Web Security
- OWASP Top 10
Web Quality Audit
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- addyosmani
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/addyosmani/web-quality-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。