home / skills / bobmatnyc / claude-mpm-skills / netlify

This skill helps you deploy Jamstack sites with Netlify, using forms, identity, and serverless functions for scalable web apps.

npx playbooks add skill bobmatnyc/claude-mpm-skills --skill netlify

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
19.1 KB
---
name: netlify-deployment-platform
description: "Netlify JAMstack deployment platform with serverless functions, forms, and identity"
progressive_disclosure:
  entry_point:
    summary: "Netlify JAMstack deployment platform with serverless functions, forms, and identity"
    when_to_use: "When working with netlify-deployment-platform or related functionality."
    quick_start: "1. Review the core concepts below. 2. Apply patterns to your use case. 3. Follow best practices for implementation."
---
# Netlify Platform Skill

---
progressive_disclosure:
  entry_point:
    summary: "JAMstack deployment platform with serverless functions, forms, and identity"
    when_to_use:
      - "When deploying static sites and SPAs"
      - "When building JAMstack applications"
      - "When needing serverless functions"
      - "When requiring built-in forms and auth"
    quick_start:
      - "npm install -g netlify-cli"
      - "netlify login"
      - "netlify init"
      - "netlify deploy --prod"
  token_estimate:
    entry: 70-85
    full: 3800-4800
---

## Netlify Fundamentals

### Core Concepts
- **Sites**: Static sites deployed to Netlify's global CDN
- **Builds**: Automated build process triggered by Git commits
- **Deploy Contexts**: production, deploy-preview, branch-deploy
- **Atomic Deploys**: All-or-nothing deployments with instant rollback
- **Instant Cache Invalidation**: CDN cache cleared automatically

### Platform Benefits
- **Global CDN**: Built-in content delivery network
- **Continuous Deployment**: Auto-deploy from Git
- **HTTPS by Default**: Free SSL certificates
- **Deploy Previews**: Preview every pull request
- **Serverless Functions**: Backend logic without servers
- **Forms & Identity**: Built-in features for common needs

## Static Site Deployment

### Supported Frameworks
```bash
# React (Create React App, Vite)
Build command: npm run build
Publish directory: build (CRA) or dist (Vite)

# Next.js (Static Export)
Build command: npm run build && npm run export
Publish directory: out

# Vue.js
Build command: npm run build
Publish directory: dist

# Gatsby
Build command: gatsby build
Publish directory: public

# Hugo
Build command: hugo
Publish directory: public

# Svelte/SvelteKit
Build command: npm run build
Publish directory: build
```

### Manual Deployment
```bash
# Install Netlify CLI
npm install -g netlify-cli

# Login to Netlify
netlify login

# Initialize site
netlify init

# Deploy draft (preview URL)
netlify deploy

# Deploy to production
netlify deploy --prod

# Deploy with build
netlify deploy --build --prod
```

## netlify.toml Configuration

### Basic Configuration
```toml
# netlify.toml
[build]
  # Build command
  command = "npm run build"

  # Publish directory
  publish = "dist"

  # Functions directory
  functions = "netlify/functions"

# Production context
[context.production]
  command = "npm run build:prod"

[context.production.environment]
  NODE_ENV = "production"
  API_URL = "https://api.example.com"

# Deploy Preview context
[context.deploy-preview]
  command = "npm run build:preview"

# Branch deploys
[context.branch-deploy]
  command = "npm run build"
```

### Build Settings
```toml
[build]
  command = "npm run build"
  publish = "dist"

  # Base directory for monorepos
  base = "packages/web"

  # Ignore builds on certain changes
  ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF packages/web"

[build.environment]
  NODE_VERSION = "18"
  NPM_VERSION = "9"
  RUBY_VERSION = "3.1"
```

### Advanced Build Configuration
```toml
[build]
  command = "npm run build"
  publish = "dist"

  # Build processing
  [build.processing]
    skip_processing = false
  [build.processing.css]
    bundle = true
    minify = true
  [build.processing.js]
    bundle = true
    minify = true
  [build.processing.images]
    compress = true
```

## Environment Variables

### Setting Variables
```bash
# Via CLI
netlify env:set API_KEY "secret-value"
netlify env:set NODE_ENV "production"

# List variables
netlify env:list

# Import from .env file
netlify env:import .env
```

### Variable Scopes
```toml
# netlify.toml
[context.production.environment]
  API_URL = "https://api.production.com"
  ENABLE_ANALYTICS = "true"

[context.deploy-preview.environment]
  API_URL = "https://api.staging.com"
  ENABLE_ANALYTICS = "false"

[context.branch-deploy.environment]
  API_URL = "https://api.dev.com"
```

### Accessing in Build
```javascript
// During build
const apiUrl = process.env.API_URL;

// Client-side (must be prefixed)
const publicKey = process.env.REACT_APP_PUBLIC_KEY;
```

## Serverless Functions

### Function Structure
```javascript
// netlify/functions/hello.js
exports.handler = async (event, context) => {
  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: 'Hello from Netlify Function',
      path: event.path,
      method: event.httpMethod,
    }),
  };
};
```

### TypeScript Functions
```typescript
// netlify/functions/api.ts
import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';

interface RequestBody {
  name: string;
  email: string;
}

export const handler: Handler = async (
  event: HandlerEvent,
  context: HandlerContext
) => {
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 405,
      body: 'Method Not Allowed',
    };
  }

  const { name, email }: RequestBody = JSON.parse(event.body || '{}');

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: `Hello ${name}`,
      email,
    }),
  };
};
```

### Advanced Function Patterns
```javascript
// netlify/functions/database.js
const { MongoClient } = require('mongodb');

let cachedDb = null;

async function connectToDatabase() {
  if (cachedDb) return cachedDb;

  const client = await MongoClient.connect(process.env.MONGODB_URI);
  cachedDb = client.db();
  return cachedDb;
}

exports.handler = async (event) => {
  const db = await connectToDatabase();
  const users = await db.collection('users').find({}).toArray();

  return {
    statusCode: 200,
    body: JSON.stringify(users),
  };
};
```

### Scheduled Functions
```javascript
// netlify/functions/scheduled.js
const { schedule } = require('@netlify/functions');

const handler = async () => {
  console.log('Running scheduled task');

  // Your scheduled logic
  await performBackup();

  return {
    statusCode: 200,
  };
};

// Run every day at midnight
exports.handler = schedule('0 0 * * *', handler);
```

### Background Functions
```javascript
// netlify/functions/background-task.js
// Auto-runs in background if response is 200 within 10s
exports.handler = async (event) => {
  // Long-running task
  await processLargeDataset();

  return {
    statusCode: 200,
  };
};

// Invoke: POST to /.netlify/functions/background-task
```

## Netlify Forms

### HTML Form
```html
<!-- Contact form -->
<form name="contact" method="POST" data-netlify="true">
  <input type="hidden" name="form-name" value="contact" />

  <label>Name: <input type="text" name="name" required /></label>
  <label>Email: <input type="email" name="email" required /></label>
  <label>Message: <textarea name="message" required></textarea></label>

  <button type="submit">Send</button>
</form>
```

### React Form
```jsx
// ContactForm.jsx
import { useState } from 'react';

export default function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });

  const handleSubmit = async (e) => {
    e.preventDefault();

    const response = await fetch('/', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        'form-name': 'contact',
        ...formData,
      }).toString(),
    });

    if (response.ok) {
      alert('Thank you for your message!');
    }
  };

  return (
    <form name="contact" onSubmit={handleSubmit} data-netlify="true">
      <input type="hidden" name="form-name" value="contact" />
      {/* Form fields */}
    </form>
  );
}
```

### Form Features
```html
<!-- Spam filtering with honeypot -->
<form name="contact" method="POST" data-netlify="true" data-netlify-honeypot="bot-field">
  <input type="hidden" name="form-name" value="contact" />
  <p class="hidden">
    <label>Don't fill this out: <input name="bot-field" /></label>
  </p>
  <!-- Form fields -->
</form>

<!-- reCAPTCHA v2 -->
<form name="contact" method="POST" data-netlify="true" data-netlify-recaptcha="true">
  <div data-netlify-recaptcha="true"></div>
  <button type="submit">Submit</button>
</form>

<!-- File uploads -->
<form name="file-upload" method="POST" data-netlify="true" enctype="multipart/form-data">
  <input type="file" name="file" />
  <button type="submit">Upload</button>
</form>
```

### Form Notifications
```toml
# netlify.toml
[[plugins]]
  package = "@netlify/plugin-emails"

  [plugins.inputs]
    formName = "contact"
    to = "[email protected]"
    subject = "New contact form submission"
```

## Netlify Identity

### Enable Identity
```javascript
// Add to HTML
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>

// Initialize
if (window.netlifyIdentity) {
  window.netlifyIdentity.on('init', user => {
    if (!user) {
      window.netlifyIdentity.on('login', () => {
        document.location.href = '/admin/';
      });
    }
  });
}
```

### React Integration
```jsx
// useNetlifyIdentity.js
import { useEffect, useState } from 'react';

export function useNetlifyIdentity() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const netlifyIdentity = window.netlifyIdentity;

    netlifyIdentity.on('init', user => setUser(user));
    netlifyIdentity.on('login', user => setUser(user));
    netlifyIdentity.on('logout', () => setUser(null));

    netlifyIdentity.init();
  }, []);

  return {
    user,
    login: () => window.netlifyIdentity.open('login'),
    logout: () => window.netlifyIdentity.logout(),
    signup: () => window.netlifyIdentity.open('signup'),
  };
}
```

### Protected Functions
```javascript
// netlify/functions/protected.js
exports.handler = async (event, context) => {
  const { user } = context.clientContext;

  if (!user) {
    return {
      statusCode: 401,
      body: 'Unauthorized',
    };
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Protected data',
      user: user.email,
    }),
  };
};
```

## Redirects and Rewrites

### _redirects File
```text
# _redirects file in publish directory

# Redirect with status code
/old-path  /new-path  301

# Rewrite (proxy)
/api/*  https://api.example.com/:splat  200

# SPA fallback
/*  /index.html  200

# Force HTTPS
http://example.com/*  https://example.com/:splat  301!

# Conditional redirects
/news/*  /blog/:splat  302  Country=us

# Role-based redirects
/admin/*  /admin/dashboard  200!  Role=admin
/admin/*  /unauthorized  401
```

### netlify.toml Redirects
```toml
[[redirects]]
  from = "/old-path"
  to = "/new-path"
  status = 301

[[redirects]]
  from = "/api/*"
  to = "https://api.example.com/:splat"
  status = 200
  force = true

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[redirects]]
  from = "/admin/*"
  to = "/admin/dashboard"
  status = 200
  conditions = {Role = ["admin"]}

# Proxy with headers
[[redirects]]
  from = "/proxy/*"
  to = "https://backend.com/:splat"
  status = 200
  force = true
  [redirects.headers]
    X-From = "Netlify"
```

### Custom Headers
```toml
[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    Content-Security-Policy = "default-src 'self'"

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
```

## Deploy Previews

### Automatic Preview URLs
```toml
# netlify.toml
[context.deploy-preview]
  command = "npm run build:preview"

[context.deploy-preview.environment]
  NODE_ENV = "preview"
  API_URL = "https://api-staging.example.com"
```

### Branch Deploys
```toml
# Deploy specific branches
[context.staging]
  command = "npm run build:staging"

[context.staging.environment]
  API_URL = "https://api-staging.example.com"

# Branch pattern matching
[context.branch-deploy]
  command = "npm run build"
```

### Deploy Notifications
```bash
# GitHub PR comments
# Slack notifications
# Email notifications
# Configured in Netlify UI
```

## Split Testing (A/B Testing)

### Configuration
```toml
# netlify.toml
[[redirects]]
  from = "/*"
  to = "/version-a/:splat"
  status = 200
  conditions = {Cookie = ["ab_test=a"]}
  force = true

[[redirects]]
  from = "/*"
  to = "/version-b/:splat"
  status = 200
  conditions = {Cookie = ["ab_test=b"]}
  force = true

# 50/50 split
[[redirects]]
  from = "/*"
  to = "/version-a/:splat"
  status = 200!
  percentage = 50

[[redirects]]
  from = "/*"
  to = "/version-b/:splat"
  status = 200!
```

## Edge Functions

### Deno Runtime
```typescript
// netlify/edge-functions/hello.ts
import type { Context } from "https://edge.netlify.com";

export default async (request: Request, context: Context) => {
  const url = new URL(request.url);

  return new Response(`Hello from ${url.pathname}`, {
    headers: { "content-type": "text/html" },
  });
};

export const config = { path: "/hello" };
```

### Geolocation
```typescript
// netlify/edge-functions/geo.ts
import type { Context } from "https://edge.netlify.com";

export default async (request: Request, context: Context) => {
  const { city, country } = context.geo;

  return Response.json({
    location: `${city}, ${country}`,
  });
};
```

### Transform Response
```typescript
// netlify/edge-functions/transform.ts
import type { Context } from "https://edge.netlify.com";

export default async (request: Request, context: Context) => {
  const response = await context.next();
  const text = await response.text();

  // Modify HTML
  const modified = text.replace(
    '</body>',
    '<script>console.log("Injected by edge");</script></body>'
  );

  return new Response(modified, response);
};

export const config = { path: "/*" };
```

## Custom Domains and SSL

### Add Custom Domain
```bash
# Via CLI
netlify domains:add example.com

# DNS Configuration
# A record: 75.2.60.5
# CNAME: <site-name>.netlify.app

# Verify domain
netlify domains:verify example.com
```

### SSL Certificates
```toml
# Automatic HTTPS (default)
# Free Let's Encrypt certificates
# Auto-renewal

# Force HTTPS redirect
[[redirects]]
  from = "http://example.com/*"
  to = "https://example.com/:splat"
  status = 301
  force = true
```

## Analytics

### Netlify Analytics
```html
<!-- Automatically injected, no code needed -->
<!-- Server-side analytics, no client-side JS -->
```

### Custom Analytics
```javascript
// Track custom events
function trackEvent(eventName, data) {
  fetch('/.netlify/functions/analytics', {
    method: 'POST',
    body: JSON.stringify({ event: eventName, ...data }),
  });
}

trackEvent('button_click', { button: 'signup' });
```

## CLI Advanced Usage

### Development Server
```bash
# Run functions locally
netlify dev

# Specific port
netlify dev --port 3000

# Live session sharing
netlify dev --live

# Functions only
netlify functions:serve
```

### Site Management
```bash
# Link existing site
netlify link

# Create new site
netlify sites:create

# List sites
netlify sites:list

# Site info
netlify status

# Open site in browser
netlify open
```

### Deploy Management
```bash
# List deploys
netlify deploy:list

# Rollback to previous deploy
netlify rollback

# Cancel deploy
netlify deploy:cancel <deploy-id>
```

## Git Integration

### Continuous Deployment
```toml
# netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

# Auto-publish on Git push
# Production: main/master branch
# Previews: all pull requests
# Branch deploys: configured branches
```

### Deploy Hooks
```bash
# Trigger builds via webhook
curl -X POST -d {} https://api.netlify.com/build_hooks/<hook-id>

# Scheduled builds (use external cron + webhook)
```

## Best Practices

### Performance Optimization
```toml
# Enable processing
[build.processing]
  skip_processing = false

[build.processing.css]
  bundle = true
  minify = true

[build.processing.js]
  bundle = true
  minify = true

[build.processing.images]
  compress = true

# Asset optimization
[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
```

### Security Headers
```toml
[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    X-XSS-Protection = "1; mode=block"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "geolocation=(), microphone=(), camera=()"
    Content-Security-Policy = """
      default-src 'self';
      script-src 'self' 'unsafe-inline';
      style-src 'self' 'unsafe-inline';
      img-src 'self' data: https:;
      font-src 'self' data:;
    """
```

### Function Best Practices
```javascript
// Keep functions lightweight
// Use connection pooling
// Cache external API responses
// Set appropriate timeouts
// Handle errors gracefully

exports.handler = async (event) => {
  try {
    // Set timeout
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 8000);

    const response = await fetch(API_URL, {
      signal: controller.signal,
    });

    clearTimeout(timeout);

    return {
      statusCode: 200,
      body: JSON.stringify(await response.json()),
    };
  } catch (error) {
    console.error('Function error:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal server error' }),
    };
  }
};
```

### Build Optimization
```toml
[build]
  command = "npm run build"
  publish = "dist"

  # Skip builds when not needed
  ignore = """
    git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- . ':(exclude)docs/' ':(exclude)*.md'
  """

# Cache dependencies
[build.environment]
  NPM_FLAGS = "--legacy-peer-deps"
  NODE_OPTIONS = "--max-old-space-size=4096"
```

### Monorepo Support
```toml
# netlify.toml
[build]
  base = "packages/web"
  command = "npm run build"
  publish = "dist"

  # Only build when package changes
  ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF packages/web"
```

## Common Patterns

### SPA with API Proxy
```toml
[build]
  command = "npm run build"
  publish = "build"
  functions = "netlify/functions"

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
```

### Microsite with Subfolder
```toml
[[redirects]]
  from = "/blog/*"
  to = "https://blog.example.com/:splat"
  status = 200
  force = true
```

### Authentication Gateway
```toml
[[redirects]]
  from = "/app/*"
  to = "/app/dashboard"
  status = 200
  conditions = {Role = ["user"]}

[[redirects]]
  from = "/app/*"
  to = "/login"
  status = 302
```

---

**Summary**: Netlify provides a complete JAMstack platform with static hosting, serverless functions, forms, and identity management. Key strengths include atomic deploys, instant cache invalidation, deploy previews, and built-in CDN. Configure via netlify.toml for builds, redirects, headers, and environment-specific settings. Leverage serverless functions for backend logic, forms for user input, and Edge Functions for edge computing. Best practices include performance optimization, security headers, and efficient build configurations.

Overview

This skill packages practical guidance for deploying JAMstack sites on Netlify, including static site builds, serverless functions, forms, identity, redirects, edge functions, and configuration. It distills common commands, netlify.toml patterns, environment variable handling, and deployment workflows into actionable steps. Use it to accelerate setup, CI/CD, and production-ready features for modern web projects.

How this skill works

The skill inspects typical Netlify workflows and configuration patterns and provides the commands, file examples, and recipes you need to deploy and operate sites. It explains build settings, environment scopes, function structure (JavaScript/TypeScript), scheduled/background functions, and edge function patterns. It also covers forms, Identity integration, redirects/headers, split testing, and custom domains with SSL so you can implement those features reliably.

When to use it

  • Deploy static sites, SPAs, or pre-rendered frameworks (React, Vue, Gatsby, Hugo, Svelte).
  • Add serverless backend logic without provisioning servers (Netlify Functions or Edge Functions).
  • Collect form submissions, handle uploads, or add spam protection quickly.
  • Protect routes and functions with Netlify Identity and role-based redirects.
  • Configure redirects, rewrites, headers, A/B tests, and cache policies.

Best practices

  • Keep netlify.toml in repository root with context blocks for production, deploy-preview, and branch-deploy.
  • Store secrets in Netlify environment variables and reference context-specific variables during builds. Do not commit secrets.
  • Use atomic deploys and deploy-previews to validate PRs before merging. Configure build commands and base for monorepos.
  • Write functions with cold-start mitigation (cached DB clients) and prefer background/scheduled functions for long tasks.
  • Use _redirects or netlify.toml for rewrites, SPA fallback, and role-based access; add strict security headers via headers config.

Example use cases

  • Continuous deploy from Git: automatic builds, deploy previews, and instant rollbacks on production deploys.
  • Serverless API: connect TypeScript or JavaScript Netlify Functions to a managed database with connection caching.
  • Forms and notifications: capture contact or file-upload forms, enable honeypot or reCAPTCHA, and send notifications via plugins.
  • Identity-protected endpoints: require logged-in users for sensitive function responses and redirect based on Role conditions.
  • Edge-based personalization: run lightweight Deno edge functions to geolocate users or modify HTML responses at the CDN edge.

FAQ

How do I set environment variables per deploy context?

Define them in netlify.toml under [context.<name>.environment] or use netlify env:set to add variables; client-side values must be prefixed for exposure.

Where do serverless functions live and how are they invoked?

Place functions in the functions directory (e.g., netlify/functions). They deploy as /.netlify/functions/<name> and can be written in JS/TS; background and scheduled patterns are supported.

How do I enable previews for pull requests?

Netlify auto-creates deploy previews for PRs when connected to a Git provider. Use context.deploy-preview in netlify.toml to customize build behavior or environment variables.