Adios
BlogNext.js SaaS

Next.js SaaS

Come creare un SaaS Next.js per la produzione: autenticazione, fatturazione, job e distribuzione

Crea un SaaS Next.js 16 pronto per la produzione con autenticazione, Postgres, abbonamenti, attività in background, configurazione sicura, test e distribuzione.

Team di AdiosAggiornato 17 luglio 202610 min di lettura

Un SaaS diventa complesso dove le funzionalità incontrano lo stato: identità, autorizzazione, fatturazione, nuovi tentativi, migrazioni e rilasci. L'albero dei componenti React è solo una parte del sistema.

Definisci il primo ciclo completo del cliente

Inizia con un ciclo che il cliente possa completare: scoprire il prodotto, creare un account, completare l'onboarding, creare la risorsa principale, ricevere il risultato e tornare in seguito ritrovando lo stesso stato. Questo fa emergere i veri confini del sistema prima di un lungo elenco di funzionalità.

Descrivi le transizioni di stato accanto alle schermate. Specifica chi può eseguire ogni transizione, quali record cambiano, quali chiamate esterne avvengono e cosa succede se una chiamata si ripete o fallisce. Un SaaS affidabile è progettato attorno a queste transizioni, anziché a una raccolta di schede della dashboard.

  • —Route pubbliche di acquisizione clienti e documentazione.
  • —Autenticazione, sessione e recupero account.
  • —L'oggetto principale del dominio e le sue regole di appartenenza.
  • —Un diritto di accesso legato alla fatturazione o limiti espliciti del piano gratuito.
  • —Evidenze di email, job, audit e guasti.

Separa codice pubblico, codice applicativo e codice esclusivo del server

Usa i gruppi di route per assegnare layout diversi alle pagine di marketing e alle route autenticate dell'applicazione senza modificarne gli URL. Mantieni pagine e layout come Server Components e aggiungi Client Components attorno ai moduli e ai controlli che richiedono lo stato del browser. Così i contenuti pubblici rimangono scansionabili e la dashboard richiede meno JavaScript.

Metti accesso al database, autorizzazione, adattatori di fatturazione e integrazioni che usano segreti in moduli esclusivi del server. Un livello di accesso ai dati concentra le letture sicure in un punto verificabile. Le Server Actions gestiscono le modifiche avviate dall'interfaccia React; i Route Handlers gestiscono webhook, verifiche dello stato e interfacce HTTP usate al di fuori di quell'interfaccia.

A practical SaaS route tree

src/app/
├── (marketing)/page.tsx
├── (marketing)/pricing/page.tsx
├── (auth)/login/page.tsx
├── (app)/dashboard/page.tsx
├── (app)/projects/[id]/page.tsx
├── api/stripe/webhook/route.ts
└── api/health/route.ts

src/lib/
├── auth.ts
├── dal.ts
├── db.ts
└── billing.ts

Modella l'appartenenza nel database

Usa un database relazionale per account, appartenenze, record del dominio, diritti di accesso, eventi webhook e job che richiedono transazioni e vincoli. Assegna a ogni record una chiave dell'account o del tenant proprietario. Imponi unicità e chiavi esterne nel database, così la concorrenza non può aggirare le ipotesi del codice applicativo.

Le migrazioni sono codice di produzione. Introduci prima modifiche additive, popola separatamente i dati preesistenti quando necessario, distribuisci codice capace di leggere la struttura transitoria e rimuovi i vecchi campi in seguito. Un rilascio non deve presumere che tutte le repliche e tutti i job passino al nuovo schema nello stesso istante.

Realizza insieme autenticazione e autorizzazione

Usa una libreria di autenticazione mantenuta, a meno che gestire direttamente hashing delle password, flussi dei provider, rotazione delle sessioni, recupero e autenticazione a più fattori sia centrale per il prodotto. Conserva i dati di sessione in cookie sicuri HTTP-only e risolvi l'identità corrente sul server.

L'autenticazione non concede accesso a tutti i record. Verifica l'autorizzazione nel livello di accesso ai dati, in ogni Server Action e in ogni Route Handler protetto. Proxy può effettuare un reindirizzamento preliminare per le richieste chiaramente non autenticate, ma i controlli di sicurezza devono restare accanto ai dati e alle modifiche.

export async function getProject(projectId: string) {
  const session = await verifySession();
  const membership = await getMembership(session.userId);

  return db.project.findFirst({
    where: {
      id: projectId,
      accountId: membership.accountId,
    },
  });
}

Tratta la fatturazione come stato asincrono

Checkout avvia un processo di fatturazione; non determina lo stato definitivo dell'abbonamento. Crea la Checkout Session sul server, reindirizza al provider e aggiorna i diritti di accesso locali a partire da eventi webhook verificati. Salva gli ID del cliente e dell'abbonamento del provider accanto all'account proprietario.

Gestisci i nuovi tentativi in sicurezza registrando gli ID degli eventi con un vincolo di unicità. Tratta esplicitamente attivazione, cambi di piano, pagamenti non riusciti, annullamento ed eliminazione. Decidi quali azioni richiedono un diritto di accesso attivo e come un periodo di tolleranza influisce sull'accesso. L'interfaccia deve leggere lo stato locale normalizzato della fatturazione, anziché chiamare il provider per ogni pagina.

Sposta fuori dalle richieste il lavoro lento che può richiedere nuovi tentativi

Email, importazioni, esportazioni, sincronizzazione con provider e generazione di report non devono tenere aperta una richiesta HTTP. Salva un job persistente o emetti un evento di workflow come parte della modifica, restituisci uno stato utile all'utente e lascia che un worker esegua il lavoro lento con nuovi tentativi e scadenze.

Rendi i job idempotenti, registra i tentativi e distingui gli errori del provider per cui è possibile riprovare dagli input non validi. La dashboard deve mostrare stati in attesa, riusciti e non riusciti, senza fingere che ogni attività in background si completi immediatamente.

  • —Identità stabile del job e chiave di deduplicazione.
  • —Tentativi limitati con backoff.
  • —Timeout per le chiamate esterne.
  • —Un errore finale ispezionabile e un percorso di recupero.

Verifica confini e recupero

Usa test unitari per le regole del dominio, test di integrazione per il livello di accesso ai dati e le modifiche, e test nel browser per registrazione, onboarding, workflow principale e fatturazione. Aggiungi casi avversi: un utente richiede il record di un altro account, un webhook si ripete, operazioni concorrenti contendono un vincolo del database e un provider esterno va in timeout.

Esegui la compilazione di produzione separatamente da lint, controllo dei tipi e test. Avvia l'app compilata con valori di ambiente simili a quelli di produzione, applica le migrazioni in un passaggio controllato e verifica con smoke test contenuti pubblici, letture autenticate, una modifica, la route del webhook e il comportamento dei controlli di stato.

Distribuisci l'intero contratto dell'ambiente di esecuzione

Adios esegue il server di produzione standard di Next.js come processo Node.js persistente, così Server Components, Route Handlers, connessioni al database e pagine autenticate condividono un unico rilascio dell'applicazione. Il manifest registra compilazione, avvio, porta, percorso di verifica dello stato, risorse e riferimenti ai segreti accanto al sorgente.

Distribuisci un'anteprima, esamina i log della compilazione e dell'ambiente di esecuzione, verifica migrazioni e servizi necessari, poi promuovi la versione quando la route di verifica dello stato risponde correttamente. Domini personalizzati e TLS gestito rimangono associati alla versione promossa. Se una candidata non si avvia o non supera la verifica dello stato, le evidenze rimangono disponibili senza renderla la versione pubblica sana.

adios.yaml

name: northstar-saas
build_cmd: npm ci && npm run build
start_cmd: npm start

runtime:
  name: node@24
  port: 3000
  health_path: /api/health

requires:
  - db

env:
  DATABASE_URL: secret://DATABASE_URL
  AUTH_SECRET: secret://AUTH_SECRET
  STRIPE_SECRET_KEY: secret://STRIPE_SECRET_KEY
Tutti gli articoli