Pourquoi combiner Firebase et Next.js ?
Firebase offre un écosystème backend complet incluant authentification, base de données et stockage. Couplé aux capacités de rendu côté serveur (SSR) de Next.js, cette synergie améliore significativement les performances initiales et le référencement naturel des applications web. Les pages sont servies depuis le serveur, réduisant le temps d'attente pour l'utilisateur final et facilitant l'indexation par les moteurs de recherche.
Configuration Initiale
Assurez-vous d'avoir Node.js (version 14 ou supérieure) et un projet Next.js créé. Ensuite, installez le SDK Firebase dans votre projet :
npm install firebase
Créez un fichier de configuration, par exemple firebase.env.js, pour stocker les identifiants de votre projet Firebase :
// firebase.env.js
export const firebaseConfiguration = {
apiKey: "VOTRE_CLE_API",
authDomain: "VOTRE_DOMAINE_AUTH",
projectId: "VOTRE_IDENTIFIANT_PROJET",
storageBucket: "VOTRE_BUCKET_STOCKAGE",
messagingSenderId: "VOTRE_ID_EXPEDITEUR",
appId: "VOTRE_ID_APP"
};
Initialisation Contrôlée de Firebase
Pour éviter les ré-initialisations multiples et gérer correctement l'environnement SSR, créez un module centralisé. L'initialisation doit être unique et sécurisée.
// lib/firebase-client.js
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { firebaseConfiguration } from "../firebase.env";
let firebaseInstance = null;
export function obtainFirebaseApp() {
if (!firebaseInstance && getApps().length === 0) {
firebaseInstance = initializeApp(firebaseConfiguration);
} else if (!firebaseInstance) {
firebaseInstance = getApps()[0];
}
return firebaseInstance;
}
export function getFirebaseAuth() {
const app = obtainFirebaseApp();
return getAuth(app);
}
export function getFirestoreDatabase() {
const app = obtainFirebaseApp();
return getFirestore(app);
}
Utilisation dans le Rendu Côté Serveur
Dans les fonctions de récupération de données de Next.js comme getServerSideProps, initilaisez Firebase et interrogez votre base de données Firestore de manière asynchrone.
// pages/index.js
import { collection, getDocs } from "firebase/firestore";
import { getFirestoreDatabase } from "../lib/firebase-client";
export async function getServerSideProps(context) {
const firestore = getFirestoreDatabase();
const publicationsRef = collection(firestore, "articles");
const snapshot = await getDocs(publicationsRef);
const articlesList = snapshot.docs.map(document => ({
id: document.id,
...document.data()
}));
return {
props: { articles: articlesList }
};
}
export default function PagePrincipale({ articles }) {
return (
<main>
<h1>Publications</h1>
<ul>
{articles.map(article => (
<li key={article.id}>{article.titre}</li>
))}
</ul>
</main>
);
}
Résolution des Problèmes Courants
Erreurs de chargement Proto en SSR : Cette anomalie dans les environnements comme Vercel a été corrigée dans les versions récentes du SDK Firebase (à partir de la v9.6.0). Veillez à mettre à jour votre dépendance.
Compatibilité de App Check en SSR : Les mises à jour récentes du package Firebase ont ajouté un support explicite pour l'environnement Node.js utilisé par le SSR, permettant une utilisation correcte de getToken.
Firebase Messaging et Node.js : Des problèmes de bundles spécifiques ont été adressés par l'inclusion de bundles CJS dans le SDK, assurant une meilleure compatibilité avec les pipelines SSR.
Pratiques Recommandées
Chargement Différé des Services : Sur le client, chargez dynamiquement les modules Firebase uniquement quand ils sont nécessaires pour alléger le bundle JavaScript initial.
// Exemple de chargement dynamique pour l'authentification
async function chargerAuthentification() {
const { getAuth } = await import("firebase/auth");
const { obtainFirebaseApp } = await import("./lib/firebase-client");
const app = obtainFirebaseApp();
return getAuth(app);
}
Route API Next.js comme Proxy : Pour ne pas exposer vos clés API côté client, créez une route API Next.js qui agit comme intermédiaire sécurisé vers Firebase.
// pages/api/donnees-prot.js
import { getFirestoreDatabase } from "../../lib/firebase-client";
import { collection, getDocs } from "firebase/firestore";
export default async function handler(request, response) {
try {
const firestore = getFirestoreDatabase();
const ref = collection(firestore, "donnees_sensibles");
const snap = await getDocs(ref);
const resultat = snap.docs.map(doc => doc.data());
response.status(200).json(resultat);
} catch (erreur) {
response.status(500).json({ message: "Erreur serveur" });
}
}
Régénération Statique Incrémentale (ISR) : Pour les données peu volatiles, combinez la récupération initiale de Firebase avec le revalidation de Next.js pour une mise à jour périodique efficace.
// pages/[slug].js
export async function getStaticProps({ params }) {
const firestore = getFirestoreDatabase();
const docRef = doc(firestore, "pages", params.slug);
const docSnap = await getDoc(docRef);
if (!docSnap.exists()) {
return { notFound: true };
}
return {
props: { contenu: docSnap.data() },
revalidate: 3600 // Régénérer la page toutes les heures
};
}