Déployer Next.js sur AWS avec SST Ion : Guide Complet Serverless

Introduction
Déployer une application Next.js sur AWS signifiait traditionnellement assembler CloudFront, Lambda@Edge, S3 et une pile de fichiers YAML Terraform ou CloudFormation. Ça fonctionne — mais c'est lent, fragile et pénible à itérer.
SST Ion (v3) change la donne. C'est un framework open-source qui vous permet de définir toute votre infrastructure AWS en TypeScript, directement aux côtés de votre code applicatif. Un seul fichier, un seul langage, zéro YAML. Il déploie votre application Next.js sur CloudFront + S3 + Lambda en utilisant OpenNext sous le capot, vous offrant une configuration serverless prête pour la production en une seule commande.
Dans ce tutoriel, vous apprendrez à :
- Initialiser SST dans un projet Next.js
- Déployer sur AWS avec CloudFront, S3 et Lambda
- Ajouter le téléchargement de fichiers S3 avec des URLs présignées
- Intégrer DynamoDB pour le stockage de données serverless
- Utiliser le développement Lambda en direct pour un feedback instantané
- Configurer des domaines personnalisés et des stages de production
- Gérer plusieurs environnements (dev, staging, production)
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ installé
- AWS CLI configuré avec vos identifiants (
aws configure) - Un compte AWS avec des permissions administrateur (ou au minimum IAM, S3, CloudFront, Lambda, DynamoDB)
- Des connaissances de base en Next.js App Router et TypeScript
- Un éditeur de code (VS Code recommandé)
SST déploie de vraies ressources AWS qui peuvent engendrer des coûts. Les ressources de ce tutoriel restent dans le Free Tier AWS dans la plupart des cas, mais surveillez toujours votre tableau de bord de facturation.
Ce que vous allez construire
À la fin de ce tutoriel, vous aurez :
- Une application Next.js déployée sur AWS via CloudFront (CDN mondial)
- Un téléchargement de fichiers via S3 avec des URLs présignées
- Une table DynamoDB pour stocker des données
- Un environnement de développement Lambda en direct avec rechargement instantané
- Des environnements staging et production séparés
- Un domaine personnalisé avec SSL automatique
Étape 1 : Créer le projet Next.js
Commencez par créer une nouvelle application Next.js :
npx create-next-app@latest sst-nextjs-app
cd sst-nextjs-appSélectionnez les options suivantes :
- TypeScript : Oui
- ESLint : Oui
- Tailwind CSS : Oui
- Répertoire
src/: Oui - App Router : Oui
- Alias d'import : @/*
Étape 2 : Initialiser SST
Installez SST dans le projet :
npx sst@latest initQuand on vous le demande, sélectionnez aws comme fournisseur. Cela génère un fichier sst.config.ts à la racine du projet :
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "sst-nextjs-app",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
};
},
async run() {
new sst.aws.Nextjs("MyWeb");
},
});Décortiquons ce qui se passe ici :
app()configure le nom de l'application, la politique de suppression et le fournisseur principalremoval: "retain"conserve les ressources lors de la suppression d'un stage de production (filet de sécurité)protectempêche la suppression accidentelle des ressources de productionasync run()définit votre infrastructure — actuellement juste un site Next.jssst.aws.Nextjsdéploie votre application via OpenNext (CloudFront + S3 + Lambda)
SST crée également un répertoire .sst/ pour les types et l'état. Ajoutez-le à .gitignore :
echo ".sst" >> .gitignoreÉtape 3 : Déployer sur AWS (Premier déploiement)
Déployez l'application sur un stage de développement :
npx sst deploy --stage devLe premier déploiement prend 3 à 5 minutes pour provisionner CloudFront, S3 et Lambda. Les déploiements suivants sont beaucoup plus rapides. Une fois terminé, SST affiche votre URL CloudFront :
✓ Complete
MyWeb: https://d1234abcd.cloudfront.net
Ouvrez l'URL — votre application Next.js est en ligne sur AWS.
Chaque stage obtient son propre ensemble isolé de ressources AWS. Vous pouvez créer autant de stages que nécessaire : dev, staging, production, pr-123, etc.
Étape 4 : Ajouter le téléchargement de fichiers S3
Ajoutons un bucket S3 pour le téléchargement de fichiers et lions-le à l'application Next.js.
4.1 Mettre à jour la configuration SST
Modifiez sst.config.ts pour ajouter un bucket S3 public :
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "sst-nextjs-app",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
};
},
async run() {
const bucket = new sst.aws.Bucket("Uploads", {
access: "public",
});
new sst.aws.Nextjs("MyWeb", {
link: [bucket],
});
return {
bucket: bucket.name,
};
},
});La propriété link est la magie de SST. Elle accorde à vos fonctions serveur Next.js les permissions IAM sur le bucket et injecte le nom du bucket comme variable d'environnement — pas de politiques IAM manuelles ni de fichiers .env à gérer.
4.2 Installer le SDK AWS et le SDK SST
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner sst4.3 Créer la route API de téléchargement
Créez une Server Action qui génère une URL présignée pour le téléchargement direct vers S3 :
// src/app/api/upload/route.ts
import { Resource } from "sst";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { NextResponse } from "next/server";
const s3 = new S3Client({});
export async function POST(request: Request) {
const { filename, contentType } = await request.json();
const key = `uploads/${Date.now()}-${filename}`;
const command = new PutObjectCommand({
Bucket: Resource.Uploads.name,
Key: key,
ContentType: contentType,
});
const presignedUrl = await getSignedUrl(s3, command, {
expiresIn: 3600,
});
return NextResponse.json({
presignedUrl,
key,
publicUrl: `https://${Resource.Uploads.name}.s3.amazonaws.com/${key}`,
});
}Remarquez Resource.Uploads.name — SST injecte automatiquement le nom du bucket lié. Pas de valeurs codées en dur, pas de variables d'environnement à gérer.
4.4 Créer l'interface de téléchargement
// src/app/upload/page.tsx
"use client";
import { useState } from "react";
export default function UploadPage() {
const [file, setFile] = useState<File | null>(null);
const [uploading, setUploading] = useState(false);
const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);
async function handleUpload() {
if (!file) return;
setUploading(true);
try {
const res = await fetch("/api/upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
const { presignedUrl, publicUrl } = await res.json();
await fetch(presignedUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
setUploadedUrl(publicUrl);
} catch (error) {
console.error("Échec du téléchargement:", error);
} finally {
setUploading(false);
}
}
return (
<div className="max-w-md mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">Téléchargement de fichiers</h1>
<input
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
className="mb-4 block w-full"
/>
<button
onClick={handleUpload}
disabled={!file || uploading}
className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
>
{uploading ? "Téléchargement..." : "Envoyer vers S3"}
</button>
{uploadedUrl && (
<div className="mt-4 p-4 bg-green-50 rounded">
<p className="text-sm text-green-800">Téléchargement réussi !</p>
<a
href={uploadedUrl}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline text-sm break-all"
>
{uploadedUrl}
</a>
</div>
)}
</div>
);
}Étape 5 : Ajouter DynamoDB pour le stockage de données
5.1 Définir la table dans la configuration SST
Mettez à jour sst.config.ts pour ajouter une table DynamoDB :
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "sst-nextjs-app",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
};
},
async run() {
const bucket = new sst.aws.Bucket("Uploads", {
access: "public",
});
const table = new sst.aws.Dynamo("Notes", {
fields: {
userId: "string",
noteId: "string",
},
primaryIndex: { hashKey: "userId", rangeKey: "noteId" },
});
new sst.aws.Nextjs("MyWeb", {
link: [bucket, table],
});
return {
bucket: bucket.name,
table: table.name,
};
},
});5.2 Installer le client DynamoDB
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb5.3 Créer l'API des notes
// src/app/api/notes/route.ts
import { Resource } from "sst";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
PutCommand,
QueryCommand,
} from "@aws-sdk/lib-dynamodb";
import { NextResponse } from "next/server";
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const userId = searchParams.get("userId") || "anonymous";
const result = await client.send(
new QueryCommand({
TableName: Resource.Notes.name,
KeyConditionExpression: "userId = :userId",
ExpressionAttributeValues: { ":userId": userId },
})
);
return NextResponse.json({ notes: result.Items || [] });
}
export async function POST(request: Request) {
const { userId = "anonymous", title, content } = await request.json();
const note = {
userId,
noteId: `note_${Date.now()}`,
title,
content,
createdAt: new Date().toISOString(),
};
await client.send(
new PutCommand({
TableName: Resource.Notes.name,
Item: note,
})
);
return NextResponse.json({ note });
}5.4 Créer la page des notes
// src/app/notes/page.tsx
"use client";
import { useEffect, useState } from "react";
interface Note {
noteId: string;
title: string;
content: string;
createdAt: string;
}
export default function NotesPage() {
const [notes, setNotes] = useState<Note[]>([]);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
async function loadNotes() {
const res = await fetch("/api/notes?userId=anonymous");
const data = await res.json();
setNotes(data.notes);
}
async function createNote() {
if (!title || !content) return;
await fetch("/api/notes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, content }),
});
setTitle("");
setContent("");
loadNotes();
}
useEffect(() => {
loadNotes();
}, []);
return (
<div className="max-w-2xl mx-auto p-8">
<h1 className="text-2xl font-bold mb-6">Notes Serverless</h1>
<div className="mb-8 space-y-3">
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Titre de la note"
className="w-full border rounded px-3 py-2"
/>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Contenu de la note"
className="w-full border rounded px-3 py-2 h-24"
/>
<button
onClick={createNote}
className="bg-blue-600 text-white px-4 py-2 rounded"
>
Ajouter une note
</button>
</div>
<div className="space-y-4">
{notes.map((note) => (
<div key={note.noteId} className="border rounded p-4">
<h2 className="font-semibold">{note.title}</h2>
<p className="text-gray-600 mt-1">{note.content}</p>
<p className="text-xs text-gray-400 mt-2">{note.createdAt}</p>
</div>
))}
</div>
</div>
);
}Étape 6 : Développement Lambda en direct
C'est ici que SST brille vraiment. Au lieu de redéployer à chaque modification du code serveur, sst dev redirige les invocations Lambda vers votre machine locale en temps réel.
Lancez l'environnement de développement :
npx sst devSST fait trois choses simultanément :
- Déploie l'infrastructure sur AWS (S3, DynamoDB, CloudFront)
- Redirige les requêtes Lambda vers votre machine locale (rechargement instantané)
- Lance le serveur de développement Next.js sur
localhost:3000
Modifiez une route API, sauvegardez le fichier et rafraîchissez — le changement est immédiat. Pas de redéploiement, pas d'attente. C'est possible car SST remplace la fonction Lambda par un stub qui transfère les invocations vers votre machine via une connexion WebSocket.
Le développement Lambda en direct nécessite que vos identifiants AWS soient configurés. SST crée une connexion WebSocket légère via IoT pour le proxy — votre code s'exécute localement mais accède aux vraies ressources AWS.
Étape 7 : Configuration du domaine personnalisé
7.1 Avec Route 53
Si votre domaine est géré par Route 53, ajoutez l'option domain au composant Nextjs :
new sst.aws.Nextjs("MyWeb", {
link: [bucket, table],
domain: {
name: "app.votredomaine.com",
dns: sst.aws.dns(),
},
});SST crée automatiquement les enregistrements Route 53 et provisionne un certificat SSL via ACM.
7.2 Avec Cloudflare DNS
Pour les domaines gérés par Cloudflare :
new sst.aws.Nextjs("MyWeb", {
link: [bucket, table],
domain: {
name: "app.votredomaine.com",
dns: sst.cloudflare.dns(),
},
});7.3 Avec un DNS externe
Pour d'autres fournisseurs, SST affiche les enregistrements DNS requis et attend que vous les configuriez :
new sst.aws.Nextjs("MyWeb", {
link: [bucket, table],
domain: {
name: "app.votredomaine.com",
dns: false,
},
});Étape 8 : Configuration par environnement
Les stages SST vous permettent de créer des environnements isolés. Utilisez la variable $app.stage pour configurer les ressources différemment selon le stage :
async run() {
const isProd = $app.stage === "production";
const bucket = new sst.aws.Bucket("Uploads", {
access: "public",
});
const table = new sst.aws.Dynamo("Notes", {
fields: {
userId: "string",
noteId: "string",
},
primaryIndex: { hashKey: "userId", rangeKey: "noteId" },
pointInTimeRecovery: isProd,
});
new sst.aws.Nextjs("MyWeb", {
link: [bucket, table],
domain: isProd
? { name: "app.votredomaine.com", dns: sst.aws.dns() }
: undefined,
server: {
memory: isProd ? "1024 MB" : "512 MB",
},
});
}Déployez sur différents stages :
# Développement
npx sst deploy --stage dev
# Staging
npx sst deploy --stage staging
# Production
npx sst deploy --stage productionChaque stage crée des ressources complètement isolées — des buckets S3 séparés, des tables DynamoDB, des distributions CloudFront et des fonctions Lambda.
Étape 9 : Ajouter une tâche planifiée
SST facilite l'ajout de tâches planifiées. Ajoutons un cron job qui nettoie les anciens fichiers :
// Ajoutez dans sst.config.ts à l'intérieur de async run()
new sst.aws.Cron("CleanupOldUploads", {
schedule: "rate(1 day)",
job: {
handler: "src/functions/cleanup.handler",
link: [bucket],
},
});Créez le handler :
// src/functions/cleanup.ts
import { Resource } from "sst";
import {
S3Client,
ListObjectsV2Command,
DeleteObjectsCommand,
} from "@aws-sdk/client-s3";
const s3 = new S3Client({});
const DAYS_TO_KEEP = 30;
export async function handler() {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - DAYS_TO_KEEP);
const objects = await s3.send(
new ListObjectsV2Command({
Bucket: Resource.Uploads.name,
Prefix: "uploads/",
})
);
const toDelete = (objects.Contents || [])
.filter((obj) => obj.LastModified && obj.LastModified < cutoff)
.map((obj) => ({ Key: obj.Key! }));
if (toDelete.length > 0) {
await s3.send(
new DeleteObjectsCommand({
Bucket: Resource.Uploads.name,
Delete: { Objects: toDelete },
})
);
console.log(`${toDelete.length} anciens fichiers supprimés`);
}
return { deleted: toDelete.length };
}Étape 10 : Déploiement en production
10.1 Configuration SST finale
Voici le fichier sst.config.ts complet avec toutes les ressources :
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "sst-nextjs-app",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
};
},
async run() {
const isProd = $app.stage === "production";
const bucket = new sst.aws.Bucket("Uploads", {
access: "public",
});
const table = new sst.aws.Dynamo("Notes", {
fields: {
userId: "string",
noteId: "string",
},
primaryIndex: { hashKey: "userId", rangeKey: "noteId" },
pointInTimeRecovery: isProd,
});
new sst.aws.Nextjs("MyWeb", {
link: [bucket, table],
domain: isProd
? { name: "app.votredomaine.com", dns: sst.aws.dns() }
: undefined,
server: {
memory: isProd ? "1024 MB" : "512 MB",
architecture: "arm64",
},
});
new sst.aws.Cron("CleanupOldUploads", {
schedule: "rate(1 day)",
job: {
handler: "src/functions/cleanup.handler",
link: [bucket],
},
});
return {
bucket: bucket.name,
table: table.name,
};
},
});10.2 Déployer en production
npx sst deploy --stage production10.3 Surveiller votre déploiement
SST affiche tous les détails des ressources après le déploiement. Vous pouvez aussi les consulter dans la console AWS :
- CloudFront — votre distribution CDN et domaine
- S3 — votre bucket de téléchargement et assets statiques
- Lambda — vos fonctions serveur
- DynamoDB — votre table de données
10.4 Supprimer un stage
Pour détruire un stage non-production et supprimer toutes ses ressources :
npx sst remove --stage devLes stages de production avec protect: true ne peuvent pas être supprimés accidentellement. Vous devez d'abord définir protect à false dans la configuration avant de supprimer.
Dépannage
"Cannot find module 'sst'" dans Next.js
Assurez-vous d'avoir installé le package sst :
npm install sstLe premier déploiement est lent
Le déploiement initial provisionne une distribution CloudFront, ce qui prend 3 à 5 minutes. Les déploiements suivants sont beaucoup plus rapides (généralement moins de 60 secondes).
Erreurs "Access Denied"
Vérifiez que vos identifiants AWS ont des permissions suffisantes. SST nécessite l'accès à IAM, S3, CloudFront, Lambda, DynamoDB, CloudWatch et SSM au minimum. L'utilisation d'un rôle administrateur pour le développement est recommandée.
Le dev en direct ne se connecte pas
Vérifiez que vos identifiants AWS sont valides et que le port 443 sortant n'est pas bloqué par votre pare-feu. SST utilise AWS IoT Core pour la connexion WebSocket.
SST vs autres options de déploiement
| Fonctionnalité | SST Ion | Vercel | AWS CDK | Terraform |
|---|---|---|---|---|
| Langage | TypeScript | N/A (UI) | TypeScript | HCL |
| Dev en direct | Oui (sub-seconde) | Non | Non | Non |
| Liaison de ressources | Automatique | Variables env manuelles | Manuel | Manuel |
| Coût | Prix AWS uniquement | Par siège + usage | Prix AWS | Prix AWS |
| Support Next.js | Complet (OpenNext) | Natif | Manuel | Manuel |
| Multi-fournisseur | Plus de 150 | Vercel uniquement | AWS uniquement | Plus de 150 |
Prochaines étapes
Maintenant que vous avez une application Next.js full-stack sur AWS, envisagez :
- Ajouter l'authentification avec
sst.aws.Cognitoou intégrer NextAuth.js - Mettre en place un pipeline CI/CD avec GitHub Actions exécutant
npx sst deploy --stage production - Ajouter une API Gateway avec
sst.aws.ApiGatewayV2pour des APIs autonomes - Explorer la console SST sur
console.sst.devpour un tableau de bord visuel de vos ressources - Ajouter une file d'attente avec
sst.aws.Queuepour le traitement en arrière-plan
Conclusion
SST Ion transforme le déploiement AWS d'un casse-tête DevOps en une expérience conviviale pour les développeurs. En définissant l'infrastructure en TypeScript aux côtés de votre code applicatif, vous obtenez la sécurité des types, la liaison de ressources et le développement en direct — le tout en déployant sur votre propre compte AWS sans surcoût.
Les points clés :
- Infrastructure as Code en TypeScript — pas de YAML, pas de fichiers de configuration séparés
- La liaison de ressources élimine les politiques IAM manuelles et les variables d'environnement
- Le développement Lambda en direct fournit un feedback instantané sans redéploiement
- Les stages vous donnent des environnements isolés gratuitement
- OpenNext déploie Next.js sur AWS avec un support complet des fonctionnalités
SST vous permet de posséder votre infrastructure sans la complexité traditionnelle d'AWS. Commencez avec npx sst@latest init et déployez votre première application en quelques minutes.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Construire des fonctions durables et des workflows pilotés par événements avec Inngest et Next.js
Apprenez à construire des workflows fiables et pilotés par événements avec Inngest et Next.js. Ce tutoriel couvre les fonctions durables, les workflows par étapes, les patterns de fan-out, les retentatives, la planification et le déploiement en production.

Neon Serverless Postgres avec Next.js App Router : construire une application full-stack avec le branching de base de données
Apprenez à construire une application Next.js full-stack alimentée par Neon Serverless Postgres. Ce tutoriel couvre le driver serverless Neon, le branching de base de données pour les déploiements preview, le connection pooling et les patterns prêts pour la production.

Construire des tâches de fond en production avec Trigger.dev v3 et Next.js
Apprenez à construire des tâches de fond fiables, des tâches planifiées et des workflows multi-étapes avec Trigger.dev v3 et Next.js. Ce tutoriel couvre la création de tâches, la gestion des erreurs, les retries, les cron jobs et le déploiement en production.