Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik20 min czytania

Netlify - Kompletny Przewodnik po Platformie Jamstack

Netlify to wiodąca platforma do hostingu i automatyzacji stron Jamstack. CI/CD z GitHub, serverless functions, edge computing, formularze bez backendu, deploy previews i globalna sieć CDN.

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

  1. Instant deploys - Strona na CDN w sekundy, atomic deploys
  2. Deploy Previews - Każdy PR dostaje unikalny preview URL
  3. Serverless Functions - Backend bez zarządzania serwerami
  4. Edge Functions - Logika na edge, minimalna latencja
  5. Netlify Forms - Formularze bez backendu
  6. Split Testing - A/B testy natywnie
  7. Rollbacks - Jeden klik do poprzedniej wersji
  8. Branch deploys - Każdy branch ma osobny URL

Netlify vs Vercel vs Cloudflare Pages

CechaNetlifyVercelCloudflare Pages
Cena (hobby)Free tierFree tierFree 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/mo6000/moUnlimited
Bandwidth (free)100GB100GBUnlimited
Analytics✅ Paid✅ Paid✅ Free

Szybki start

Metoda 1: Deploy z Git (zalecane)

Code
Bash
# 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 deploy

Metoda 2: Netlify CLI

Code
Bash
# 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 dev

Metoda 3: Drag & Drop

Code
Bash
# 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
TOML
# 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)

Code
TEXT
# 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=en

Netlify Functions

Podstawowa funkcja

TSnetlify/functions/hello.ts
TypeScript
// 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=Jan

POST request z body

TSnetlify/functions/create-user.ts
TypeScript
// 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)

TSnetlify/functions/daily-report.ts
TypeScript
// 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 UTC

Background Functions

TSnetlify/functions/process-webhook-background.ts
TypeScript
// 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.ts

Funkcja z bazą danych

TSnetlify/functions/users.ts
TypeScript
// 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

TSnetlify/edge-functions/hello.ts
TypeScript
// 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

TSnetlify/edge-functions/geolocation.ts
TypeScript
// 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

TSnetlify/edge-functions/auth-middleware.ts
TypeScript
// 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

TSnetlify/edge-functions/ab-test.ts
TypeScript
// 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

TSnetlify/edge-functions/personalize.ts
TypeScript
// 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

Code
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)

TScomponents/ContactForm.tsx
TypeScript
// 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 form

Formularz z honeypot (spam protection)

Code
HTML
<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

Code
HTML
<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
TOML
# netlify.toml

# Email notification
[build.environment]
  NETLIFY_FORM_EMAIL = "your@email.com"

# Webhook notification
# Ustaw w Dashboard: Forms → Form notifications → Outgoing webhook

Netlify Identity

Konfiguracja

Code
JavaScript
// 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

TShooks/useAuth.ts
TypeScript
// 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

TSnetlify/functions/protected-data.ts
TypeScript
// 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
TOML
# 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

Code
YAML
# 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

Code
Bash
# 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:create

Local development

Code
Bash
# 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

Code
Bash
# 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 --open

Environment variables

Code
Bash
# 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 .env

Build Plugins

Popularne pluginy

netlify.toml
TOML
# 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

JSplugins/my-plugin/index.js
JavaScript
// 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)
  }
}
Code
TOML
# Użycie własnego pluginu
[[plugins]]
  package = "./plugins/my-plugin"
  [plugins.inputs]
    apiKey = "xxx"

Integracje z frameworkami

Next.js

Code
Bash
# Automatyczna konfiguracja
# Netlify wykrywa Next.js i konfiguruje automatycznie

# netlify.toml
[build]
  command = "npm run build"
  publish = ".next"

[[plugins]]
  package = "@netlify/plugin-nextjs"

Astro

Code
Bash
# 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
TOML
# netlify.toml
[build]
  command = "gatsby build"
  publish = "public"

[[plugins]]
  package = "@netlify/plugin-gatsby"

Nuxt

Code
Bash
# Nuxt 3
npm install -D @netlify/nitro

# nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'netlify'
  }
})

SvelteKit

Code
Bash
# 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
TOML
# 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 ruchu

Split testing z Edge Functions

TSnetlify/edge-functions/split-test.ts
TypeScript
// 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óbLimit
Bandwidth100GB/miesiąc
Build minutes300/miesiąc
Concurrent builds1
Serverless Functions125K invocations/miesiąc
Edge Functions3M invocations/miesiąc
Forms100 submissions/miesiąc
Identity1000 users
SitesUnlimited

Pro ($19/miesiąc per member)

ZasóbLimit
Bandwidth1TB/miesiąc
Build minutes25,000/miesiąc
Concurrent builds3
Serverless Functions125K (w cenie), potem $25/1M
Forms1000 submissions/miesiąc
AnalyticsIncluded
Background FunctionsIncluded
Priority supportIncluded

Business ($99/miesiąc per member)

ZasóbLimit
Bandwidth1.5TB/miesiąc
Build minutes30,000/miesiąc
Concurrent builds5
SAML SSOIncluded
Audit logsIncluded
99.99% SLAIncluded
Priority supportIncluded

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?

  1. Lokalnie: netlify dev uruchamia functions z hot reload
  2. Logi: Functions → Function logs w panelu Netlify
  3. netlify functions:invoke do testowania z CLI
  4. 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?

  1. Sprawdź vercel.json → przekonwertuj na netlify.toml
  2. API routes → Netlify Functions
  3. Middleware → Edge Functions
  4. Połącz repo z Netlify
  5. Zaktualizuj zmienne środowiskowe
  6. 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.