netlify-development について
Netlify プロジェクトの開発・デプロイ向けガイドで、各種関数、データ保存、画像 CDN、設定を扱います。関数の種類ごとの用途と制約に加え、Blobs の保存範囲や一貫性も説明します。
主な機能
- 01サーバーレス、Edge、バックグラウンド、定期実行関数の指針
- 02関数のパスと設定の提案
- 03Netlify Blobs のストレージ実践
- 04Image CDN のパラメーターと使い方
- 05デプロイ設定と実行制限の説明
活用シーン
- 01API エンドポイントに適した関数タイプの選択
- 02エッジでのリクエスト処理とパーソナライズの実装
- 03Blobs を使ったアプリデータの保存
- 04定期タスクと画像最適化の設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mindrally/ skills --skill netlify-developmentインストール後、Claude Code で/netlify-developmentを入力して読み込まれたか確認します。
詳細
Netlify 開発のベストプラクティス
概要
このスキルでは、サーバーレス関数、エッジ関数、バックグラウンド関数、スケジュール関数、Netlify Blobs、Image CDN、デプロイ設定など、Netlify でプロジェクトを構築してデプロイするための包括的なガイドラインを紹介します。
基本原則
- コード内のエクスポートされた
configオブジェクトによる設定を使用します(netlify.toml より推奨) - インポートする Netlify パッケージにバージョン番号を追加しないでください(
@netlify/functionsを使用し、@netlify/[email protected]は使用しません) - 明示的に必要な場合にのみ CORS ヘッダーを追加します
- ユースケースに応じて適切な種類の関数を活用します
- 状態とデータの保存には Netlify Blobs を使用します
.netlifyフォルダーはツールによって生成される出力であり、ユーザーコードではありません。.gitignoreに含めてください- サーバーレス関数やエッジ関数を publish/public ディレクトリ内に配置しないでください
- 明示的に依頼されない限り、デフォルトの functions または edge-functions ディレクトリを変更しないでください
関数の種類の概要
| 種類 | ユースケース | タイムアウト | パスの規則 |
|---|---|---|---|
| サーバーレス | 標準的な API エンドポイント | 10s(Pro は 26s) | /.netlify/functions/name |
| エッジ | リクエスト/レスポンスの変更 | CPU 50ms | カスタムパス |
| バックグラウンド | 実行時間の長い非同期タスク | 15 分 | -background サフィックス |
| スケジュール | cron ベースのタスク | 30s | スケジュールで設定 |
関数とエッジ関数のソースディレクトリは、デフォルトでは netlify/functions と netlify/edge-functions ですが、netlify.toml で変更できます。
[functions]
directory = "my_functions"
[build]
edge_functions = "my-edge-functions"
netlify.toml の設定は、UI のビルド設定より優先されます。
サーバーレス関数
基本構造
// 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} - 設定でカスタムパスを指定すると、デフォルトのパスは完全に置き換えられます
- よりわかりやすい API URL にするには、カスタムパスを使用します
Context 引数
サーバーレス関数とエッジ関数はいずれも、第 2 引数として context オブジェクトを受け取ります。
{
account: { id: string }; // Netlify team account ID
cookies: {
get: (name: string) => string | undefined;
set: (options: { name: string; value: string; path?: string; domain?: string; secure?: boolean; httpOnly?: boolean; expires?: Date }) => void;
delete: (nameOrOptions: string | { name: string; path?: string; domain?: string }) => void;
};
deploy: { context: string; id: string; published: boolean };
geo: {
city: string;
country: { code: string; name: string };
latitude: number;
longitude: number;
subdivision: { code: string; name: string };
timezone: string;
postalCode: string;
ip: string;
};
params: Record<string, string>; // route params from the path config
requestId: string;
server: { region: string };
site: { id: string; name: string; url: string };
}
Netlify グローバルオブジェクト
すべてのサーバーレス関数とエッジ関数でグローバルスコープから利用できます。実行時に環境変数を読み書きする場合は、process.env より Netlify.env.* を優先してください。
{
context: object | null; // same as the function's second arg; null outside a handler
env: {
get: (name: string) => string | undefined;
has: (name: string) => boolean;
set: (name: string, value: string) => void;
delete: (name: string) => void;
toObject: () => Record<string, string>;
};
}
エッジ関数
ユースケース
- オリジンに到達する前にリクエストを変更する
- ユーザーに返す前にレスポンスを変更する
- ジオロケーションに基づくパーソナライズ
- 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,
});
};
ランタイムとモジュールのサポート
エッジ関数は Node.js ではなく Deno で実行され、複数のエコシステムからコードを取り込めます。
- Node.js の組み込みモジュール:
import { randomBytes } from 'node:crypto' - Deno モジュール:URL インポート。例:
import React from 'https://esm.sh/react' - npm パッケージ(ベータ版):
npm installでインストールし、名前でインポートします。ネイティブバイナリを含むパッケージ(Prisma など)や動的 require を使用するパッケージは、動作しない場合があります - インポートマップを使うと、完全な URL の代わりに短い名前でサードパーティのモジュールを参照できます。
netlify.tomlの[functions] deno_import_map = "./import_map.json"でマップファイルのパスを宣言します
利用できる Web API は Node.js より限定されています。fetch/Request/Response/URL、TextEncoder/TextDecoder(およびストリーム版)、Web Crypto(randomUUID、getRandomValues、SubtleCrypto)、WebSocket、setTimeout/setInterval、Streams API、URLPattern を利用できます。
追加の設定オプション
path と excludedPath に加えて、エッジ関数の設定では次のオプションを利用できます。
{
pattern?: RegExp | RegExp[]; // regex alternative to `path`
excludedPattern?: RegExp | RegExp[];
method?: string | string[];
onError?: 'continue' | 'fail' | 'fallback';
cache?: 'manual'; // opt in to edge response caching
}
複数のエッジ関数の順序
同じパスに一致するエッジ関数が複数ある場合、明示的に上から順に実行するために netlify.toml で宣言します(キャッシュされた関数は位置にかかわらず、常に最後に実行されます)。
[[edge_functions]]
path = "/admin"
function = "auth"
[[edge_functions]]
path = "/admin"
function = "injector"
cache = "manual"
実行順序:設定ベースの関数はフレームワークによって生成された関数より先に実行され、フレームワークによって生成された関数はユーザーが定義したインライン関数より先に実行されます。キャッシュされていない関数はキャッシュされた関数より先に実行されます。複数のインライン関数はファイル名のアルファベット順に実行されます。fetch()/URL() で関数を再実行するのではなく、context.next() を使ってチェーンを続行します(これらの関数は新しいリクエストチェーンを開始します)。
制限と互換性のない機能
- 圧縮後のコードサイズは20 MB、デプロイあたりのメモリは512 MB、リクエストあたりのCPU時間は50ms、レスポンスヘッダーのタイムアウトは40秒です。
- スプリットテスト、
_headers/netlify.tomlのカスタムヘッダー、エッジ関数が処理するパスでのプリレンダリングには対応していません。 - リクエストの書き換え先は同一サイトのURLに限られます。外部コンテンツには
fetch()を使用してください。 - Netlify の HIPAA 準拠ホスティングサービスには含まれません。
バックグラウンド関数
主な特徴
- タイムアウトは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 });
};
スケジュール関数
主な特徴
- CRON 式は UTC で評価され、最小間隔は1分です。
- 実行時間の上限は30秒です。関数はレスポンスボディを返しません(戻り値は無視されます)。
- リクエストボディは
next_runフィールドを含む JSON エンコードされたオブジェクトです。このフィールドには、次回のスケジュール実行の ISO-8601 タイムスタンプが格納されます。 - スケジュール関数は公開済みのデプロイでのみ実行されます。デプロイプレビューやブランチデプロイでは実行されません。
netlify devとnetlify functions:invoke <name>を同時に実行して、ローカルでテストできます。- スケジュールは、ほかの設定との一貫性を保つために
netlify.tomlに記述することもできますが、コード内のconfig.scheduleを推奨します。[functions."daily-cleanup"] schedule = "@daily"
設定
// 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
データにリレーショナル構造や検索が必要でない限り、新しいデータベースを立ち上げるより Blobs を優先してください。Blobs はプロビジョニング不要で、すべてのコンピューティングタイプで同じ API を使用できます。制限:ストア名は64バイト以下、オブジェクトキーは600バイト以下、オブジェクトの最大サイズは5GBです。
一貫性とストレージのスコープ
- デフォルトでは、ストアは結果整合性です。読み取りは高速で、書き込みは約60秒以内に反映されます。即時の書き込み後読み取りを可能にするには、
getStoreに{ consistency: 'strong' }を渡しますが、読み取り速度は遅くなります。 - 組み込みの同時実行制御はありません。最後の書き込みが優先されます。同時に書き込むプロセスが互いのデータを上書きしないようにする必要がある場合は、独自のロックを追加してください。
- デプロイ固有のストア(
getDeployStore)はデプロイと同期し、そのデプロイが削除されると削除されます。グローバルストア(getStore)はすべてのブランチで共有され、自動削除されることはありません。ビルドプラグインとファイルベースのアップロードでは、デプロイ固有のストアに書き込む必要があります。 - グローバルストアに書き込む場合は、
context.deploy.contextの値に応じて処理を分岐し、本番環境以外のデータが漏れないようにしてください。function getBlobStore(...args: Parameters<typeof getStore>) { return context.deploy.context === 'production' ? getStore(...args) : getDeployStore(...args); }
基本的な使用方法
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' });
デプロイ固有とサイト全体のストア
// 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
使用方法
<img src="/.netlify/images?url=/images/hero.jpg&w=800&q=80" alt="Hero">
<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}`;
}
リモート画像、カスタムルート、キャッシュ
- 外部の画像ドメインは変換前に許可リストに登録する必要があります。また、絶対 URL のみが必要です:
[images] remote_images = ["https://externalexample.com/.*"] urlパラメーターがリモート画像を指す場合は、その値を URI エンコードしてください。- 変換用の URL を
/.netlify/imagesより分かりやすいものにするには、リダイレクトを定義します(パラメーターは動的なままでも固定してもかまいません):[[redirects]] from = "/transform-my-images/*" to = "/.netlify/images?url=/:splat&w=50&h=50" status = 200 - カスタムヘッダー(例:
Cache-Control)を適用できるのは、[[headers]]/_headersを使い、明示的に指定した場合に限り、同じドメインでホストされている画像のみです。 - 変換後の画像と元画像はエッジでキャッシュされます。新しいデプロイによってキャッシュが無効になり、ソースが変更されていれば画像が再処理されます。
- フレームワーク連携では、Image CDN が自動的に、または最小限の設定で利用できます:Angular の
NgOptimizedImage、Astro の<Image />、Nuxt のnuxt/image、next.config.jsのremotePatternsを使う Next.js、NETLIFY_IMAGE_CDN=trueを使う Gatsby。
環境変数
Functions でのアクセス
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 UI(Site configuration > Environment variables)、CLI、API、または netlify.toml で設定できます。優先順位は netlify.toml > UI/CLI/API で、サイト固有の変数は共有変数より優先されます。CLI を使用するには、まずサイトをリンクする必要があります(netlify link):
netlify env:set API_KEY "value"
netlify env:set API_KEY "secret-value" --secret
netlify env:unset API_KEY
netlify env:import .env
netlify env:list --plain --context production
Netlify のビルドでは .env ファイルは直接読み込まれません。代わりに env:import でインポートしてください。本番コンテキストの .env.local をエクスポートするのは、Git の追跡対象外であることを確認してからにしてください。また、絶対にコミットしないでください。
netlify.toml では、デプロイコンテキスト(production、deploy-preview、branch-deploy、dev、または特定のブランチ名)ごとに変数の適用範囲を設定できます:
[context.production.environment]
NODE_VERSION = "20"
[context.deploy-preview.environment]
NOT_PRIVATE_ITEM = "not so secret"
この方法で機密値をコミットするのは避け、シークレットには UI または CLI を使用してください。
ビルド設定
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
ファイルベースのアップロード
Functions への直接アップロード
// 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' });
};
サイト管理
.netlify/state.json に siteId が設定されているか確認すると、サイトがすでにリンクされているかどうかが分かります。ファイルがない場合、netlify init によってプロジェクトを Netlify に接続する手順(必要に応じてリポジトリの設定を含む)が案内されます。Netlify にサイトがすでに存在する場合は、netlify link を実行すると、ローカルフォルダーをそのサイトに接続するための認証情報の入力が求められます。
サイトの作成とリンク
# 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
Functions のローカルテスト
# 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 分であることを無視する
- サーバーレス関数で入力を検証しない
- 環境変数をハードコードする
- エッジでエラーを適切に処理しない
- エッジ関数に適したタスクにサーバーレス関数を使用する
.netlifyディレクトリを Git の追跡対象外にせず、コミットする- ユーザーから求められていない限り、新しいサイトで
netlify.toml/_redirects/_headersにリダイレクトやカスタムヘッダーを追加する - デプロイ固有の Blobs ストアではなく、グローバルスコープの Blobs ストアに本番以外のデータを書き込む