install
source · Clone the upstream repo
git clone https://github.com/MacPhobos/research-mind
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/MacPhobos/research-mind "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/toolchains-platforms-deployment-netlify" ~/.claude/skills/macphobos-research-mind-toolchains-platforms-deployment-netlify && rm -rf "$T"
manifest:
.claude/skills/toolchains-platforms-deployment-netlify/skill.mdsource content
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
# 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
# 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
# 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
[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
[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
# 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
# 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
// 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
// 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
// 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
// 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
// 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
// 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
<!-- 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
// 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
<!-- 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
# netlify.toml [[plugins]] package = "@netlify/plugin-emails" [plugins.inputs] formName = "contact" to = "admin@example.com" subject = "New contact form submission"
Netlify Identity
Enable Identity
// 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
// 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
// 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
# _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
[[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
[[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
# 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
# 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
# GitHub PR comments # Slack notifications # Email notifications # Configured in Netlify UI
Split Testing (A/B Testing)
Configuration
# 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
// 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
// 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
// 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
# 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
# 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
<!-- Automatically injected, no code needed --> <!-- Server-side analytics, no client-side JS -->
Custom Analytics
// 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
# Run functions locally netlify dev # Specific port netlify dev --port 3000 # Live session sharing netlify dev --live # Functions only netlify functions:serve
Site Management
# 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
# List deploys netlify deploy:list # Rollback to previous deploy netlify rollback # Cancel deploy netlify deploy:cancel <deploy-id>
Git Integration
Continuous Deployment
# 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
# 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
# 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
[[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
// 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
[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
# 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
[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
[[redirects]] from = "/blog/*" to = "https://blog.example.com/:splat" status = 200 force = true
Authentication Gateway
[[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.