netlify-development
サーバーレス関数・エッジ関数・Blobsストレージ・ビルド設定・デプロイワークフローなど、Netlify開発におけるベストプラクティスを提供します。Netlifyを活用したプロジェクトの構築・運用時に参照することで、最適な設計・実装方針を導き出せます。
description の原文を見る
Netlify development best practices for serverless functions, edge functions, Blobs storage, build configuration, and deployment workflows.
SKILL.md 本文
Netlify開発のベストプラクティス
概要
このスキルは、Netlify でプロジェクトをビルド・デプロイするための包括的なガイドラインを提供します。サーバーレス関数、Edge 関数、バックグラウンド関数、スケジュール関数、Netlify Blobs、Image CDN、デプロイメント設定を対象としています。
コアプリンシプル
- エクスポートされた
configオブジェクトによるインコード設定を使用(netlify.toml より推奨) - インポートした Netlify パッケージにバージョン番号を追加しない
- 明示的に必要な場合にのみ CORS ヘッダーを追加
- ユースケースに応じた適切な関数タイプを活用
- 状態とデータストレージに Netlify Blobs を使用
関数タイプの概要
| タイプ | ユースケース | タイムアウト | パス規則 |
|---|---|---|---|
| Serverless | 標準 API エンドポイント | 10秒(Pro は 26秒) | /.netlify/functions/name |
| Edge | リクエスト/レスポンス修正 | 50ms CPU | カスタムパス |
| Background | 長時間実行の非同期タスク | 15分 | -background サフィックス |
| Scheduled | Cron ベースのタスク | 10秒(Pro は 26秒) | スケジュール設定 |
サーバーレス関数
基本構造
// netlify/functions/hello.mts
import type { Context } from '@netlify/functions';
export default async (request: Request, context: Context) => {
try {
// Validate request
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
const body = await request.json();
// Business logic
const result = await processData(body);
return Response.json(result);
} catch (error) {
console.error('Function error:', error);
return Response.json({ error: 'Internal Server Error' }, { status: 500 });
}
};
export const config = {
path: '/api/hello',
};
設定オプション
export const config = {
// Custom path (instead of /.netlify/functions/name)
path: '/api/users',
// HTTP methods (optional, allows all by default)
method: ['GET', 'POST'],
// Rate limiting
rateLimit: {
windowSize: 60,
windowLimit: 100,
},
};
パス規則
- デフォルトパス:
/.netlify/functions/{function_name} - config によるカスタムパスはデフォルトを完全に置き換える
- より整理された API URL にはカスタムパスを使用
Edge 関数
ユースケース
- オリジンに到達する前にリクエストを修正
- ユーザーに返される前にレスポンスを修正
- 地理情報ベースのパーソナライゼーション
- A/B テスト
- エッジでの認証
実装
// netlify/edge-functions/geo-redirect.ts
import type { Context } from '@netlify/edge-functions';
export default async (request: Request, context: Context) => {
const country = context.geo.country?.code || 'US';
// Redirect based on country
if (country === 'DE') {
return Response.redirect(new URL('/de', request.url));
}
// Continue to origin
return context.next();
};
export const config = {
path: '/*',
excludedPath: ['/api/*', '/_next/*'],
};
レスポンス修正
export default async (request: Request, context: Context) => {
// Get response from origin
const response = await context.next();
// Modify headers
response.headers.set('X-Custom-Header', 'value');
// Transform HTML
const html = await response.text();
const modifiedHtml = html.replace('</body>', '<script>...</script></body>');
return new Response(modifiedHtml, {
status: response.status,
headers: response.headers,
});
};
バックグラウンド関数
主な特性
- 15分のタイムアウト(ウォールクロック時間)
- すぐに 202 ステータスコードを返す
- 戻り値は無視される
-backgroundサフィックスが必須
実装
// netlify/functions/process-video-background.mts
import type { Context } from '@netlify/functions';
import { getStore } from '@netlify/blobs';
export default async (request: Request, context: Context) => {
const { videoId } = await request.json();
// Long-running processing
const result = await processVideo(videoId);
// Store result for later retrieval
const store = getStore('processed-videos');
await store.setJSON(videoId, result);
// Return value is ignored
return new Response('Processing complete');
};
export const config = {
path: '/api/process-video',
};
バックグラウンド処理の結果取得
// netlify/functions/get-video-status.mts
import { getStore } from '@netlify/blobs';
export default async (request: Request, context: Context) => {
const url = new URL(request.url);
const videoId = url.searchParams.get('id');
const store = getStore('processed-videos');
const result = await store.get(videoId, { type: 'json' });
if (!result) {
return Response.json({ status: 'processing' });
}
return Response.json({ status: 'complete', data: result });
};
スケジュール関数
設定
// netlify/functions/daily-cleanup.mts
import type { Context } from '@netlify/functions';
export default async (request: Request, context: Context) => {
console.log('Running daily cleanup...');
// Cleanup logic
await cleanupOldRecords();
return new Response('Cleanup complete');
};
export const config = {
schedule: '@daily', // or '0 0 * * *' for midnight UTC
};
スケジュールパターン
// Common patterns
export const config = {
schedule: '@hourly', // Every hour
schedule: '@daily', // Every day at midnight
schedule: '@weekly', // Every week
schedule: '*/15 * * * *', // Every 15 minutes
schedule: '0 9 * * 1-5', // 9 AM on weekdays
};
Netlify Blobs
基本的な使用方法
import { getStore } from '@netlify/blobs';
// Get a store
const store = getStore('my-store');
// Store data
await store.set('key', 'string value');
await store.setJSON('json-key', { foo: 'bar' });
// Retrieve data
const value = await store.get('key');
const jsonValue = await store.get('json-key', { type: 'json' });
// Delete data
await store.delete('key');
// List keys
const { blobs } = await store.list();
バイナリデータ
import { getStore } from '@netlify/blobs';
const store = getStore('files');
// Store binary data
const arrayBuffer = await file.arrayBuffer();
await store.set('uploads/file.pdf', arrayBuffer, {
metadata: { contentType: 'application/pdf' },
});
// Retrieve binary data
const blob = await store.get('uploads/file.pdf', { type: 'blob' });
デプロイ固有 vs サイト全体
// Site-wide store (persists across deploys)
const siteStore = getStore({
name: 'user-data',
siteID: context.site.id,
});
// Deploy-specific store (scoped to deployment)
const deployStore = getStore({
name: 'cache',
deployID: context.deploy.id,
});
Netlify Image CDN
使用方法
<!-- Basic optimization -->
<img src="/.netlify/images?url=/images/hero.jpg&w=800&q=80" alt="Hero">
<!-- With fit and format -->
<img src="/.netlify/images?url=/images/hero.jpg&w=400&h=300&fit=cover&fm=webp" alt="Hero">
パラメーター
url: ソース画像パス(必須)w: 幅(ピクセル)h: 高さ(ピクセル)q: 品質(1-100)fit: cover、contain、fillfm: フォーマット(webp、avif、auto)
プログラムでの使用
function getOptimizedImageUrl(src: string, options: ImageOptions) {
const params = new URLSearchParams({
url: src,
w: String(options.width),
q: String(options.quality || 80),
fm: 'auto',
});
return `/.netlify/images?${params}`;
}
環境変数
関数内でのアクセス
export default async (request: Request, context: Context) => {
// Access environment variables
const apiKey = Netlify.env.get('API_KEY');
const dbUrl = process.env.DATABASE_URL;
if (!apiKey) {
console.error('API_KEY not configured');
return Response.json({ error: 'Configuration error' }, { status: 500 });
}
// Use variables
};
コンテキスト変数
export default async (request: Request, context: Context) => {
// Available context
const { site, deploy, geo, ip, requestId } = context;
console.log('Site ID:', site.id);
console.log('Deploy ID:', deploy.id);
console.log('Country:', geo.country?.code);
console.log('Request ID:', requestId);
};
ビルド設定
netlify.toml
[build]
command = "npm run build"
publish = "dist"
functions = "netlify/functions"
[build.environment]
NODE_VERSION = "20"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
[functions]
node_bundler = "esbuild"
[dev]
command = "npm run dev"
port = 3000
targetPort = 5173
ファイルベースアップロード
関数への直接アップロード
// netlify/functions/upload.mts
import { getStore } from '@netlify/blobs';
export default async (request: Request, context: Context) => {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return Response.json({ error: 'No file provided' }, { status: 400 });
}
const store = getStore('uploads');
const key = `${Date.now()}-${file.name}`;
await store.set(key, await file.arrayBuffer(), {
metadata: {
contentType: file.type,
originalName: file.name,
},
});
return Response.json({ key, message: 'Upload successful' });
};
サイト管理
サイトの作成とリンク
# Initialize new site
netlify init
# Link existing site
netlify link
# Deploy manually
netlify deploy
# Deploy to production
netlify deploy --prod
ローカル開発
Netlify Dev
# Start local development server
netlify dev
# With specific port
netlify dev --port 8888
# With live reload
netlify dev --live
関数のローカルテスト
# Invoke function directly
netlify functions:invoke hello --payload '{"name": "World"}'
# Serve functions only
netlify functions:serve
エラーハンドリングのベストプラクティス
構造化されたエラーレスポンス
interface ErrorResponse {
error: string;
code: string;
details?: unknown;
}
function errorResponse(status: number, error: ErrorResponse): Response {
return Response.json(error, { status });
}
export default async (request: Request, context: Context) => {
try {
// Validation
const body = await request.json();
if (!body.email) {
return errorResponse(400, {
error: 'Email is required',
code: 'MISSING_EMAIL',
});
}
// Business logic
const result = await processRequest(body);
return Response.json(result);
} catch (error) {
console.error('Function error:', error);
return errorResponse(500, {
error: 'Internal server error',
code: 'INTERNAL_ERROR',
});
}
};
セキュリティガイドライン
入力検証
import { z } from 'zod';
const RequestSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export default async (request: Request, context: Context) => {
const body = await request.json();
const result = RequestSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: 'Validation failed', details: result.error.issues },
{ status: 400 }
);
}
// Use validated data
const { email, name } = result.data;
};
認証
async function verifyToken(request: Request): Promise<User | null> {
const auth = request.headers.get('Authorization');
if (!auth?.startsWith('Bearer ')) {
return null;
}
const token = auth.slice(7);
// Verify token logic
return verifyJWT(token);
}
export default async (request: Request, context: Context) => {
const user = await verifyToken(request);
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// Authenticated request handling
};
よくある落とし穴
@netlify/functionsインポートにバージョン番号を追加している- 明示的に必要でない場合に CORS ヘッダーを追加している
- ユースケースに不適切な関数タイプを使用している
- バックグラウンド関数に
-backgroundサフィックスを忘れている - バックグラウンド関数で永続的なストレージに Blobs を使用していない
- バックグラウンド関数の 15分タイムアウトを無視している
- サーバーレス関数で入力検証を行っていない
- 環境変数をハードコードしている
- エッジでエラーを適切にハンドリングしていない
- エッジ関数に適したタスクにサーバーレス関数を使用している
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。