Stack Zero-Cost per Lanciare un SaaS: Vercel, Supabase, Resend e Stripe

Lanciare un SaaS senza spendere un euro al mese non è un sogno: è una strategia concreta che migliaia di indie hacker stanno usando nel 2026. Lo stack Vercel + Supabase + Resend + Stripe ti dà tutto ciò che serve — database, autenticazione, email, pagamenti — senza costi fissi, fino a quando il tuo progetto non genera revenue sufficiente per scalare. In questa guida ti mostro come collegare questi quattro servizi in un’app Next.js completa.

💡 Prerequisito: questa guida usa Next.js App Router con TypeScript. Se stai costruendo un Micro SaaS da solo, questo è lo stack consigliato per il 2026. Per il deploy con Next.js 16 e Turbopack, leggi la nostra guida dedicata.

Il Free Tier di ogni servizio — cosa ottieni davvero

Prima di iniziare, è fondamentale capire esattamente cosa include ogni piano gratuito, per evitare sorprese in produzione.

  • Vercel Hobby: 100GB banda/mese, 100 ore di build, Edge Functions illimitate, dominio .vercel.app. Limitazione principale: nessun dominio custom con team collaborators. Upgrade consigliato a ~100 utenti attivi giornalieri.
  • Supabase Free: 1 progetto, 500MB database PostgreSQL, 1GB storage, 50.000 utenti attivi Auth, Edge Functions incluse. Attenzione: il database va in pausa dopo 7 giorni di inattività — usa il keepalive o passa al piano Pro (€25/mese).
  • Resend Free: 3.000 email/mese, 1 dominio, API access completo. Per transazionali di un SaaS early-stage è più che sufficiente. Piano a pagamento da $20/mese per 50.000 email.
  • Stripe: nessun piano “free” nel senso classico — paghi solo quando incassi. Commissione EU: 1.4% + €0.25 per carta europea, 2.9% + €0.30 per carte extra-UE. Nessun canone mensile. Stripe Connect e Billing hanno tariffe diverse.

🔧 Setup iniziale: crea un account su Vercel, Supabase, Resend e Stripe. Tutti accettano signup con GitHub.

Architettura dell’app — come i 4 servizi si connettono

L’architettura è semplice: Next.js è il cervello dell’applicazione, Supabase gestisce dati e autenticazione, Stripe gestisce i pagamenti, Resend invia le email. Vercel si occupa di fare il deploy e distribuire l’app globalmente.

# Struttura progetto Next.js
my-saas/
├── app/
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   └── signup/page.tsx
│   ├── (dashboard)/
│   │   ├── layout.tsx         # Server Component — check sessione
│   │   └── page.tsx
│   ├── api/
│   │   └── webhooks/
│   │       └── stripe/route.ts # Webhook Stripe
│   └── actions/
│       ├── auth.ts            # Server Actions auth
│       └── subscription.ts    # Server Actions Stripe
├── lib/
│   ├── supabase/
│   │   ├── client.ts          # Supabase browser client
│   │   └── server.ts          # Supabase server client
│   ├── stripe.ts
│   └── resend.ts
└── middleware.ts               # Auth middleware Supabase

Setup Supabase — Auth e database

Supabase fornisce due client distinti: uno per il browser (con la sessione utente) e uno per il server (con accesso privilegiato). In Next.js App Router li usi così:

// lib/supabase/server.ts — Next.js App Router
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createSupabaseServer() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          )
        },
      },
    }
  )
}

// middleware.ts — protegge tutte le route /dashboard
import { createServerClient } from '@supabase/ssr'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  const response = NextResponse.next()
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: { /* ... */ } }
  )

  const { data: { user } } = await supabase.auth.getUser()

  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return response
}

export const config = {
  matcher: ['/dashboard/:path*'],
}

Stripe Checkout e gestione abbonamenti

Per i pagamenti, il flusso consigliato è Stripe Checkout: l’utente viene reindirizzato alla pagina di pagamento hosted di Stripe (nessun problema PCI compliance), poi torna alla tua app. I webhook confermano il pagamento lato server.

// app/actions/subscription.ts
'use server'
import Stripe from 'stripe'
import { createSupabaseServer } from '@/lib/supabase/server'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function createCheckoutSession(priceId: string) {
  const supabase = await createSupabaseServer()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Non autenticato')

  // Recupera o crea il customer Stripe
  const { data: profile } = await supabase
    .from('profiles')
    .select('stripe_customer_id')
    .eq('id', user.id)
    .single()

  let customerId = profile?.stripe_customer_id
  if (!customerId) {
    const customer = await stripe.customers.create({
      email: user.email,
      metadata: { supabase_id: user.id },
    })
    customerId = customer.id
    await supabase.from('profiles').update({ stripe_customer_id: customerId }).eq('id', user.id)
  }

  const session = await stripe.checkout.sessions.create({
    customer: customerId,
    payment_method_types: ['card'],
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'subscription',
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=1`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
  })

  return { url: session.url }
}

// app/api/webhooks/stripe/route.ts — gestisce eventi Stripe
import { stripe } from '@/lib/stripe'
import { createSupabaseAdminClient } from '@/lib/supabase/admin'
import { sendWelcomeEmail } from '@/lib/resend'

export async function POST(request: Request) {
  const body = await request.text()
  const sig = request.headers.get('stripe-signature')!
  const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!)

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object
    const supabase = createSupabaseAdminClient()

    // Aggiorna lo stato abbonamento nel DB
    await supabase.from('profiles').update({
      subscription_status: 'active',
      subscription_id: session.subscription as string,
    }).eq('stripe_customer_id', session.customer)

    // Invia email di benvenuto con Resend
    await sendWelcomeEmail(session.customer_email!, session.customer_details?.name)
  }

  return new Response('OK', { status: 200 })
}

Email transazionali con Resend

Resend si integra perfettamente con Next.js e supporta template React per le email. Il setup è minimale:

// lib/resend.ts
import { Resend } from 'resend'

const resend = new Resend(process.env.RESEND_API_KEY)

export async function sendWelcomeEmail(email: string, name?: string | null) {
  await resend.emails.send({
    from: 'noreply@tuosaas.com', // dominio verificato su Resend
    to: email,
    subject: 'Benvenuto! Il tuo account è attivo',
    html: `
      <h1>Ciao ${name ?? 'utente'}!</h1>
      <p>Il tuo abbonamento è ora attivo. Accedi alla dashboard per iniziare.</p>
      <a href="${process.env.NEXT_PUBLIC_APP_URL}/dashboard">Vai alla Dashboard</a>
    `,
  })
}

🎯 Consiglio: usa i React Email components per costruire template email con JSX. Si integra nativamente con Resend e rende le email manutenibili come qualsiasi altro componente React.

FAQ — Stack Zero-Cost SaaS

Supabase mette in pausa il DB dopo 7 giorni — come evitarlo?

La pausa si attiva solo sui progetti Supabase senza attività nel piano free. Due soluzioni: usa un cron job (Vercel Cron, che è gratuito) che fa un semplice ping al DB ogni 5-6 giorni; oppure passa al piano Pro di Supabase ($25/mese) che rimuove questo limite.

Vercel Hobby è adatto per un SaaS in produzione?

Per le prime fasi sì. I limiti principali del piano Hobby sono: nessuna collaborazione team, nessuna protezione password per i preview, limite build mensile. Quando il tuo SaaS supera i primi 50-100 utenti attivi, considera il piano Pro di Vercel ($20/mese per membro del team).

Come gestisco gli ambienti (staging/production)?

Crea due progetti Supabase separati (uno free per staging, uno free per production). Su Vercel, usa le Environment Variables per differenziare i due ambienti. Stripe ha modalità test/live nativamente — usa le chiavi test in staging.

Quando devo scalare e passare a piani a pagamento?

Regola pratica: quando generi abbastanza revenue da coprire i costi di upgrade (tipicamente €50-100/mese per tutti i servizi combinati) e hai problemi concreti legati ai limiti del piano free. Non upgradare preventivamente — ottimizza il free tier finché puoi.

Condividi

Articoli Recenti

Categorie popolari