Netlify - Kompletny Przewodnik po Platformie Jamstack
Czym jest Netlify?
Netlify to pionierska platforma, która zrewolucjonizowała sposób budowania i hostowania stron internetowych. Założona w 2014 roku przez Mathiasa Biilmanna i Christiana Bacha, Netlify wprowadziła i spopularyzowała pojęcie "Jamstack" - architekturę opartą na JavaScript, API i Markup, która oddziela frontend od backendu.
Netlify oferuje kompletny ekosystem do tworzenia nowoczesnych aplikacji webowych: automatyczny CI/CD z GitHub/GitLab/Bitbucket, globalną sieć CDN, serverless functions, edge computing, formularze bez backendu, uwierzytelnianie użytkowników i wiele więcej. Wszystko to dostępne z jednego panelu, bez konieczności zarządzania serwerami.
Platforma zdobyła popularność dzięki filozofii "developer experience first" - deploy strony to dosłownie git push, a nowa wersja jest automatycznie budowana i publikowana na globalnej sieci CDN. Ta prostota przyciągnęła miliony deweloperów i setki tysięcy firm na całym świecie.
Historia i wpływ na branżę
Netlify zostało założone jako odpowiedź na złożoność tradycyjnego hostingu webowego. Mathias Biilmann, wcześniej CTO BitBalloon, zauważył że deweloperzy tracą zbyt dużo czasu na konfigurację serwerów zamiast budować produkty.
W 2015 roku Netlify wprowadziło termin "Jamstack", promując architekturę gdzie strony są pre-renderowane i serwowane z CDN, a dynamiczna funkcjonalność jest dostarczana przez API i serverless functions. Ta filozofia zmieniła sposób myślenia o web development.
Dziś Netlify hostuje miliony stron, w tym projekty takie jak React, Vue.js, Kubernetes i wielu innych. Firma zebrała ponad 200 milionów dolarów finansowania i jest uznawana za lidera w kategorii platform Jamstack.
Dlaczego Netlify?
Kluczowe zalety
- Instant deploys - Strona na CDN w sekundy, atomic deploys
- Deploy Previews - Każdy PR dostaje unikalny preview URL
- Serverless Functions - Backend bez zarządzania serwerami
- Edge Functions - Logika na edge, minimalna latencja
- Netlify Forms - Formularze bez backendu
- Split Testing - A/B testy natywnie
- Rollbacks - Jeden klik do poprzedniej wersji
- Branch deploys - Każdy branch ma osobny URL
Netlify vs Vercel vs Cloudflare Pages
| Cecha | Netlify | Vercel | Cloudflare Pages |
|---|---|---|---|
| Cena (hobby) | Free tier | Free tier | Free tier |
| CI/CD | ✅ Built-in | ✅ Built-in | ✅ Built-in |
| Serverless Functions | ✅ | ✅ | ✅ Workers |
| Edge Functions | ✅ | ✅ | ✅ (native) |
| Forms | ✅ Native | ❌ | ❌ |
| Identity/Auth | ✅ Native | ❌ | ❌ |
| Split Testing | ✅ | ❌ | ❌ |
| Next.js support | ✅ | ✅ Najlepszy | ✅ |
| Build minutes (free) | 300/mo | 6000/mo | Unlimited |
| Bandwidth (free) | 100GB | 100GB | Unlimited |
| Analytics | ✅ Paid | ✅ Paid | ✅ Free |
Szybki start
Metoda 1: Deploy z Git (zalecane)
# 1. Połącz repozytorium
# Idź na app.netlify.com → New site from Git → GitHub/GitLab/Bitbucket
# 2. Wybierz repozytorium i branch
# 3. Konfiguruj build
Build command: npm run build
Publish directory: dist (lub build, out, .next, public)
# 4. Deploy!
# Każdy push automatycznie triggeruje nowy deployMetoda 2: Netlify CLI
# Instalacja
npm install -g netlify-cli
# Logowanie
netlify login
# Inicjalizacja projektu (w folderze projektu)
netlify init
# Deploy (produkcyjny)
netlify deploy --prod
# Deploy preview (test)
netlify deploy
# Development server z Functions
netlify devMetoda 3: Drag & Drop
# Najprostszy sposób:
# 1. Zbuduj stronę lokalnie: npm run build
# 2. Przeciągnij folder build na app.netlify.com/drop
# 3. Gotowe!Konfiguracja projektu
netlify.toml (główny plik konfiguracyjny)
# netlify.toml
# Podstawowa konfiguracja builda
[build]
command = "npm run build"
publish = "dist"
functions = "netlify/functions"
edge_functions = "netlify/edge-functions"
# Environment variables
[build.environment]
NODE_VERSION = "20"
NPM_FLAGS = "--legacy-peer-deps"
# Kontekst produkcyjny
[context.production]
command = "npm run build"
environment = { NODE_ENV = "production" }
# Kontekst staging (branch: staging)
[context.staging]
command = "npm run build:staging"
environment = { NODE_ENV = "staging" }
# Kontekst deploy-preview (PRs)
[context.deploy-preview]
command = "npm run build:preview"
# Kontekst branch-deploy (inne branche)
[context.branch-deploy]
command = "npm run build:dev"
# Nagłówki HTTP
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
# Przekierowania
[[redirects]]
from = "/old-page"
to = "/new-page"
status = 301
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
# SPA fallback
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# Proxy do zewnętrznego API
[[redirects]]
from = "/external-api/*"
to = "https://api.external.com/:splat"
status = 200
force = true
# Edge Functions routing
[[edge_functions]]
function = "geolocation"
path = "/api/location"
[[edge_functions]]
function = "auth"
path = "/dashboard/*"
# Plugins
[[plugins]]
package = "@netlify/plugin-nextjs"
[[plugins]]
package = "netlify-plugin-sitemap"
[[plugins]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs]
fail_on_grade = "B"_redirects (alternatywnie)
# Prostsza składnia dla przekierowań
# Redirect 301
/old-path /new-path 301
# Redirect z wildcard
/blog/* /articles/:splat 301
# Proxy (zachowuje oryginalny URL)
/api/* /.netlify/functions/:splat 200
# SPA fallback
/* /index.html 200
# Warunkowe przekierowanie (geo)
/pricing /pricing-us 200 Country=us
/pricing /pricing-eu 200 Country=de,fr,pl
# Warunkowe przekierowanie (język)
/* /pl/:splat 200 Language=pl
/* /en/:splat 200 Language=enNetlify Functions
Podstawowa funkcja
// netlify/functions/hello.ts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
export const handler: Handler = async (
event: HandlerEvent,
context: HandlerContext
) => {
const { name = 'World' } = event.queryStringParameters || {}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString()
})
}
}
// Wywołanie: /.netlify/functions/hello?name=JanPOST request z body
// netlify/functions/create-user.ts
import type { Handler } from '@netlify/functions'
interface CreateUserBody {
email: string
name: string
password: string
}
export const handler: Handler = async (event) => {
// Obsługa CORS preflight
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
},
body: ''
}
}
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' })
}
}
try {
const body: CreateUserBody = JSON.parse(event.body || '{}')
// Walidacja
if (!body.email || !body.name || !body.password) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing required fields' })
}
}
// Tutaj logika tworzenia użytkownika
// np. zapis do bazy danych, wysłanie emaila
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
success: true,
user: {
id: 'user_' + Date.now(),
email: body.email,
name: body.name
}
})
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' })
}
}
}Scheduled Functions (CRON)
// netlify/functions/daily-report.ts
import type { Handler } from '@netlify/functions'
export const handler: Handler = async (event, context) => {
// Sprawdź czy to scheduled invocation
const isScheduled = event.headers['x-netlify-scheduled']
if (!isScheduled) {
return {
statusCode: 403,
body: 'This function can only be triggered by schedule'
}
}
// Logika raportu
console.log('Running daily report at', new Date().toISOString())
// np. wysłanie emaila z raportem
await sendDailyReport()
return {
statusCode: 200,
body: 'Daily report sent'
}
}
// Konfiguracja w netlify.toml:
// [functions."daily-report"]
// schedule = "0 8 * * *" # Codziennie o 8:00 UTCBackground Functions
// netlify/functions/process-webhook-background.ts
import type { BackgroundHandler } from '@netlify/functions'
// Background functions mogą działać do 15 minut
export const handler: BackgroundHandler = async (event, context) => {
const payload = JSON.parse(event.body || '{}')
// Długotrwała operacja
console.log('Processing webhook:', payload.id)
// np. przetwarzanie dużego pliku
// np. wysyłanie wielu emaili
// np. synchronizacja z zewnętrznym API
for (let i = 0; i < payload.items.length; i++) {
await processItem(payload.items[i])
console.log(`Processed ${i + 1}/${payload.items.length}`)
}
// Background functions nie zwracają response
// Wywołujący dostaje natychmiast 202 Accepted
}
// Nazwa pliku musi kończyć się na -background.tsFunkcja z bazą danych
// netlify/functions/users.ts
import type { Handler } from '@netlify/functions'
import { MongoClient } from 'mongodb'
const client = new MongoClient(process.env.MONGODB_URI!)
export const handler: Handler = async (event) => {
try {
await client.connect()
const db = client.db('myapp')
const users = db.collection('users')
switch (event.httpMethod) {
case 'GET': {
const allUsers = await users.find({}).toArray()
return {
statusCode: 200,
body: JSON.stringify(allUsers)
}
}
case 'POST': {
const body = JSON.parse(event.body || '{}')
const result = await users.insertOne({
...body,
createdAt: new Date()
})
return {
statusCode: 201,
body: JSON.stringify({ id: result.insertedId })
}
}
default:
return {
statusCode: 405,
body: 'Method not allowed'
}
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Database error' })
}
} finally {
// Connection pooling - nie zamykaj w serverless
// await client.close()
}
}Edge Functions
Podstawowa Edge Function
// netlify/edge-functions/hello.ts
import type { Context } from "@netlify/edge-functions"
export default async (request: Request, context: Context) => {
const url = new URL(request.url)
const name = url.searchParams.get('name') || 'World'
return new Response(`Hello from the Edge, ${name}!`, {
headers: {
'Content-Type': 'text/plain',
'X-Edge-Location': context.geo.city || 'unknown'
}
})
}
// Konfiguracja w netlify.toml:
// [[edge_functions]]
// function = "hello"
// path = "/api/hello"Geolocation na Edge
// netlify/edge-functions/geolocation.ts
import type { Context } from "@netlify/edge-functions"
export default async (request: Request, context: Context) => {
const { geo } = context
return Response.json({
city: geo.city,
country: geo.country?.code,
countryName: geo.country?.name,
region: geo.subdivision?.code,
timezone: geo.timezone,
latitude: geo.latitude,
longitude: geo.longitude
})
}Edge Function jako Middleware
// netlify/edge-functions/auth-middleware.ts
import type { Context } from "@netlify/edge-functions"
export default async (request: Request, context: Context) => {
const authHeader = request.headers.get('Authorization')
// Sprawdź token
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Bearer' }
})
}
const token = authHeader.substring(7)
try {
// Weryfikacja tokena
const isValid = await verifyToken(token)
if (!isValid) {
return new Response('Invalid token', { status: 401 })
}
// Kontynuuj do oryginalnej strony
return context.next()
} catch (error) {
return new Response('Authentication error', { status: 500 })
}
}
// Konfiguracja w netlify.toml:
// [[edge_functions]]
// function = "auth-middleware"
// path = "/dashboard/*"A/B Testing na Edge
// netlify/edge-functions/ab-test.ts
import type { Context } from "@netlify/edge-functions"
export default async (request: Request, context: Context) => {
// Sprawdź czy użytkownik ma już przypisaną wersję
const cookies = request.headers.get('Cookie') || ''
const existingVariant = cookies.match(/ab-variant=(\w+)/)?.[1]
// Przypisz losowo jeśli brak
const variant = existingVariant || (Math.random() < 0.5 ? 'A' : 'B')
// Przekieruj do odpowiedniej wersji
const url = new URL(request.url)
if (variant === 'B') {
url.pathname = url.pathname.replace('/pricing', '/pricing-new')
}
const response = await context.rewrite(url.toString())
// Ustaw cookie jeśli nowy użytkownik
if (!existingVariant) {
response.headers.set(
'Set-Cookie',
`ab-variant=${variant}; Path=/; Max-Age=2592000`
)
}
// Dodaj header dla analytics
response.headers.set('X-AB-Variant', variant)
return response
}Personalizacja na Edge
// netlify/edge-functions/personalize.ts
import type { Context } from "@netlify/edge-functions"
export default async (request: Request, context: Context) => {
const response = await context.next()
// Tylko dla HTML
const contentType = response.headers.get('Content-Type')
if (!contentType?.includes('text/html')) {
return response
}
let html = await response.text()
// Personalizacja na podstawie lokalizacji
const { geo } = context
// Zmień walutę
const currency = getCurrencyForCountry(geo.country?.code)
html = html.replace(/\$(\d+)/g, `${currency.symbol}$1`)
// Zmień język greeting
const greeting = getGreetingForCountry(geo.country?.code)
html = html.replace('{{greeting}}', greeting)
// Dodaj banner dla konkretnego kraju
if (geo.country?.code === 'PL') {
html = html.replace(
'</head>',
`<style>.pl-banner { display: block !important; }</style></head>`
)
}
return new Response(html, {
headers: {
...Object.fromEntries(response.headers),
'Content-Type': 'text/html; charset=utf-8'
}
})
}Netlify Forms
Podstawowy formularz HTML
<!-- Formularz z data-netlify="true" -->
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<label>
Imię:
<input type="text" name="name" required />
</label>
<label>
Email:
<input type="email" name="email" required />
</label>
<label>
Wiadomość:
<textarea name="message" required></textarea>
</label>
<button type="submit">Wyślij</button>
</form>Formularz z AJAX (React)
// components/ContactForm.tsx
import { useState } from 'react'
export function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setStatus('loading')
const form = e.currentTarget
const formData = new FormData(form)
try {
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(formData as any).toString()
})
if (response.ok) {
setStatus('success')
form.reset()
} else {
setStatus('error')
}
} catch (error) {
setStatus('error')
}
}
return (
<form
name="contact"
method="POST"
data-netlify="true"
onSubmit={handleSubmit}
>
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" placeholder="Imię" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Wiadomość" required />
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Wysyłanie...' : 'Wyślij'}
</button>
{status === 'success' && <p>Dziękujemy za wiadomość!</p>}
{status === 'error' && <p>Wystąpił błąd. Spróbuj ponownie.</p>}
</form>
)
}
// WAŻNE: Dodaj statyczną wersję formularza dla SSG
// W public/index.html lub jako hidden formFormularz z honeypot (spam protection)
<form name="contact" method="POST" data-netlify="true" data-netlify-honeypot="bot-field">
<input type="hidden" name="form-name" value="contact" />
<!-- Honeypot - ukryte pole dla botów -->
<p style="display: none;">
<label>
Don't fill this out:
<input name="bot-field" />
</label>
</p>
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Wyślij</button>
</form>Formularz z reCAPTCHA
<form name="contact" method="POST" data-netlify="true" data-netlify-recaptcha="true">
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<!-- reCAPTCHA widget - Netlify wstawia automatycznie -->
<div data-netlify-recaptcha="true"></div>
<button type="submit">Wyślij</button>
</form>
<!-- Konfiguracja w Netlify Dashboard:
Site settings → Forms → Spam filters → Enable reCAPTCHA
Dodaj Site Key i Secret Key z Google reCAPTCHA -->Notyfikacje o submissions
# netlify.toml
# Email notification
[build.environment]
NETLIFY_FORM_EMAIL = "your@email.com"
# Webhook notification
# Ustaw w Dashboard: Forms → Form notifications → Outgoing webhookNetlify Identity
Konfiguracja
// Zainstaluj Netlify Identity Widget
// npm install netlify-identity-widget
import netlifyIdentity from 'netlify-identity-widget'
// Inicjalizacja
netlifyIdentity.init()
// Otwórz modal logowania
function openLogin() {
netlifyIdentity.open('login')
}
// Otwórz modal rejestracji
function openSignup() {
netlifyIdentity.open('signup')
}
// Wylogowanie
function logout() {
netlifyIdentity.logout()
}
// Nasłuchuj eventów
netlifyIdentity.on('login', (user) => {
console.log('User logged in:', user)
netlifyIdentity.close()
})
netlifyIdentity.on('logout', () => {
console.log('User logged out')
})
// Pobierz aktualnego użytkownika
const user = netlifyIdentity.currentUser()React Hook dla Identity
// hooks/useAuth.ts
import { useState, useEffect, createContext, useContext } from 'react'
import netlifyIdentity from 'netlify-identity-widget'
interface User {
id: string
email: string
user_metadata: {
full_name?: string
avatar_url?: string
}
app_metadata: {
roles?: string[]
}
token: {
access_token: string
expires_at: number
}
}
interface AuthContextType {
user: User | null
login: () => void
logout: () => void
signup: () => void
isAuthenticated: boolean
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
netlifyIdentity.init()
netlifyIdentity.on('login', (user) => {
setUser(user as User)
netlifyIdentity.close()
})
netlifyIdentity.on('logout', () => {
setUser(null)
})
// Sprawdź czy już zalogowany
const currentUser = netlifyIdentity.currentUser()
if (currentUser) {
setUser(currentUser as User)
}
}, [])
return (
<AuthContext.Provider
value={{
user,
login: () => netlifyIdentity.open('login'),
logout: () => netlifyIdentity.logout(),
signup: () => netlifyIdentity.open('signup'),
isAuthenticated: !!user
}}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}Protected Function
// netlify/functions/protected-data.ts
import type { Handler } from '@netlify/functions'
export const handler: Handler = async (event, context) => {
// Netlify automatycznie weryfikuje token
const { user } = context.clientContext || {}
if (!user) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Unauthorized' })
}
}
// Sprawdź role
const roles = user.app_metadata?.roles || []
if (!roles.includes('admin')) {
return {
statusCode: 403,
body: JSON.stringify({ error: 'Forbidden' })
}
}
// Zwróć chronione dane
return {
statusCode: 200,
body: JSON.stringify({
message: `Hello ${user.email}!`,
data: { /* sensitive data */ }
})
}
}
// Wywołanie z frontendu:
// fetch('/.netlify/functions/protected-data', {
// headers: {
// Authorization: `Bearer ${user.token.access_token}`
// }
// })Deploy Previews
Automatyczne previews
# netlify.toml
# Deploy previews dla wszystkich PR
[context.deploy-preview]
command = "npm run build:preview"
environment = { PREVIEW_MODE = "true" }
# Możesz też wyłączyć dla niektórych branchy
# [context.deploy-preview.environment]
# SKIP_PREVIEW = "true"Preview comments w GitHub
# GitHub Actions - dodaj komentarz z preview URL
name: Netlify Deploy Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2
with:
publish-dir: './dist'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy from GitHub Actions"
enable-pull-request-comment: true
enable-commit-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}Netlify CLI
Instalacja i setup
# Instalacja globalna
npm install -g netlify-cli
# Logowanie
netlify login
# Status
netlify status
# Podłączenie istniejącej strony
netlify link
# Utworzenie nowej strony
netlify sites:createLocal development
# Development server z Functions i Edge Functions
netlify dev
# Z określonym portem
netlify dev --port 8888
# Z live reload
netlify dev --live
# Tylko Functions
netlify functions:serve
# Testowanie pojedynczej funkcji
netlify functions:invoke hello --payload '{"name": "Jan"}'Deploy commands
# Deploy preview (draft)
netlify deploy
# Deploy produkcyjny
netlify deploy --prod
# Deploy z określonego folderu
netlify deploy --dir=build --prod
# Deploy z wiadomością
netlify deploy --prod --message "Release v1.2.3"
# Deploy i otwórz w przeglądarce
netlify deploy --prod --openEnvironment variables
# Lista zmiennych
netlify env:list
# Ustaw zmienną
netlify env:set API_KEY "secret-key"
# Usuń zmienną
netlify env:unset API_KEY
# Import z pliku .env
netlify env:import .envBuild Plugins
Popularne pluginy
# netlify.toml
# Next.js support
[[plugins]]
package = "@netlify/plugin-nextjs"
# Sitemap generator
[[plugins]]
package = "netlify-plugin-sitemap"
[plugins.inputs]
buildDir = "dist"
# Lighthouse CI
[[plugins]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs]
fail_on_grade = "B"
# Cache dependencies
[[plugins]]
package = "netlify-plugin-cache"
[plugins.inputs]
paths = [
"node_modules",
".cache"
]
# Algolia indexing
[[plugins]]
package = "netlify-plugin-algolia-index"
[plugins.inputs]
appId = "YOUR_APP_ID"
apiKey = "YOUR_API_KEY"
indexName = "your_index"
# Image optimization
[[plugins]]
package = "@netlify/plugin-image-optim"Własny plugin
// plugins/my-plugin/index.js
module.exports = {
// Przed buildem
onPreBuild: async ({ utils, constants, inputs }) => {
console.log('Running pre-build tasks...')
// Pobierz dane z CMS
const content = await fetchContentFromCMS()
await utils.cache.save(content, ['content.json'])
},
// Po buildzie
onPostBuild: async ({ utils, constants }) => {
console.log('Running post-build tasks...')
// Walidacja
const hasErrors = await validateBuild(constants.PUBLISH_DIR)
if (hasErrors) {
utils.build.failBuild('Build validation failed')
}
},
// Przy sukcesie
onSuccess: async ({ utils }) => {
console.log('Deploy successful!')
// Wyślij notyfikację
await sendSlackNotification('Deploy complete!')
},
// Przy błędzie
onError: async ({ utils, error }) => {
console.error('Deploy failed:', error.message)
// Wyślij alert
await sendErrorAlert(error)
}
}# Użycie własnego pluginu
[[plugins]]
package = "./plugins/my-plugin"
[plugins.inputs]
apiKey = "xxx"Integracje z frameworkami
Next.js
# Automatyczna konfiguracja
# Netlify wykrywa Next.js i konfiguruje automatycznie
# netlify.toml
[build]
command = "npm run build"
publish = ".next"
[[plugins]]
package = "@netlify/plugin-nextjs"Astro
# Instalacja adaptera
npm install @astrojs/netlify
# astro.config.mjs
import { defineConfig } from 'astro/config'
import netlify from '@astrojs/netlify'
export default defineConfig({
output: 'server', // lub 'hybrid'
adapter: netlify()
})Gatsby
# netlify.toml
[build]
command = "gatsby build"
publish = "public"
[[plugins]]
package = "@netlify/plugin-gatsby"Nuxt
# Nuxt 3
npm install -D @netlify/nitro
# nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'netlify'
}
})SvelteKit
# Instalacja adaptera
npm install -D @sveltejs/adapter-netlify
# svelte.config.js
import adapter from '@sveltejs/adapter-netlify'
export default {
kit: {
adapter: adapter()
}
}Split Testing
Konfiguracja A/B testu
# netlify.toml
# Branch-based split testing
[context.production.processing.html]
pretty_urls = true
# W Dashboard:
# Site settings → Build & deploy → Split Testing
# Dodaj branche i procent ruchuSplit testing z Edge Functions
// netlify/edge-functions/split-test.ts
import type { Context } from "@netlify/edge-functions"
const EXPERIMENTS = {
'new-checkout': {
control: 50, // 50% -> stara wersja
variant: 50 // 50% -> nowa wersja
}
}
export default async (request: Request, context: Context) => {
const cookies = request.headers.get('Cookie') || ''
const experimentCookie = cookies.match(/exp-checkout=(\w+)/)?.[1]
let variant = experimentCookie
if (!variant) {
// Przypisz losowo
const rand = Math.random() * 100
variant = rand < EXPERIMENTS['new-checkout'].control ? 'control' : 'variant'
}
// Przekieruj do odpowiedniej wersji
const url = new URL(request.url)
if (variant === 'variant') {
url.pathname = '/checkout-v2' + url.pathname.replace('/checkout', '')
}
const response = await context.rewrite(url.toString())
// Ustaw cookie
if (!experimentCookie) {
response.headers.append(
'Set-Cookie',
`exp-checkout=${variant}; Path=/; Max-Age=604800`
)
}
// Header dla analytics
response.headers.set('X-Experiment-Variant', variant)
return response
}Cennik
Free Tier
| Zasób | Limit |
|---|---|
| Bandwidth | 100GB/miesiąc |
| Build minutes | 300/miesiąc |
| Concurrent builds | 1 |
| Serverless Functions | 125K invocations/miesiąc |
| Edge Functions | 3M invocations/miesiąc |
| Forms | 100 submissions/miesiąc |
| Identity | 1000 users |
| Sites | Unlimited |
Pro ($19/miesiąc per member)
| Zasób | Limit |
|---|---|
| Bandwidth | 1TB/miesiąc |
| Build minutes | 25,000/miesiąc |
| Concurrent builds | 3 |
| Serverless Functions | 125K (w cenie), potem $25/1M |
| Forms | 1000 submissions/miesiąc |
| Analytics | Included |
| Background Functions | Included |
| Priority support | Included |
Business ($99/miesiąc per member)
| Zasób | Limit |
|---|---|
| Bandwidth | 1.5TB/miesiąc |
| Build minutes | 30,000/miesiąc |
| Concurrent builds | 5 |
| SAML SSO | Included |
| Audit logs | Included |
| 99.99% SLA | Included |
| Priority support | Included |
Enterprise (Custom)
- Unlimited builds
- Custom SLA
- Dedicated support
- Advanced security
- Custom contracts
FAQ - Najczęściej zadawane pytania
Czy Netlify jest darmowe?
Tak, Netlify oferuje hojny darmowy plan z 100GB bandwidth, 300 minut builda i podstawowymi funkcjami. Dla większości projektów hobbystycznych i małych stron biznesowych Free tier jest wystarczający.
Jak skonfigurować własną domenę?
W panelu Netlify: Site settings → Domain management → Add custom domain. Netlify automatycznie konfiguruje certyfikat SSL (Let's Encrypt). Musisz dodać rekordy DNS u swojego registrar wskazujące na Netlify.
Jak działają Serverless Functions vs Edge Functions?
Serverless Functions działają w AWS Lambda, mają dostęp do Node.js, timeout do 26 sekund (background: 15 min), idealne dla API i integracji.
Edge Functions działają na Deno w globalnej sieci edge, mają minimalną latencję (<50ms), timeout 50ms, idealne dla personalizacji, A/B testów i middleware.
Czy mogę używać Netlify dla WordPress?
Nie bezpośrednio. Netlify jest dla stron statycznych/Jamstack. Możesz jednak użyć WordPress jako headless CMS i generować statyczną stronę (np. Gatsby, Next.js) deployowaną na Netlify.
Jak debugować Netlify Functions?
- Lokalnie:
netlify devuruchamia functions z hot reload - Logi: Functions → Function logs w panelu Netlify
netlify functions:invokedo testowania z CLI- Dodaj
console.log()- wszystko trafia do logów
Czy Netlify obsługuje server-side rendering (SSR)?
Tak, przez Next.js (z @netlify/plugin-nextjs), Nuxt, SvelteKit i inne frameworki. SSR działa przez Serverless Functions. Dla lepszej wydajności rozważ Edge SSR.
Jak migrować z Vercel do Netlify?
- Sprawdź
vercel.json→ przekonwertuj nanetlify.toml - API routes → Netlify Functions
- Middleware → Edge Functions
- Połącz repo z Netlify
- Zaktualizuj zmienne środowiskowe
- Przekieruj domenę
Podsumowanie
Netlify to pionierska platforma Jamstack, która zrewolucjonizowała web development. Oferuje kompletny ekosystem: automatyczny CI/CD, globalny CDN, Serverless i Edge Functions, formularze, uwierzytelnianie i wiele więcej - wszystko z jednego panelu.
Główne zalety Netlify:
- Instant deploys - strona na CDN w sekundy
- Deploy Previews - każdy PR ma swój URL
- Zero config - automatyczna detekcja frameworków
- Edge computing - personalizacja blisko użytkownika
- Forms & Identity - backend bez backendu
- Hojny free tier - 100GB bandwidth, 300 min build
Czy to dla portfolio, strony firmowej, SaaS czy enterprise aplikacji - Netlify dostarcza niezawodną infrastrukturę i świetny developer experience.