The full-stack pick for real-time apps - chat, live feeds, collaboration.
Real-time features are the hardest thing to bolt onto a traditional stack: you need persistent connections, a way to fan out updates, and reconnection handling. This stack sidesteps all of it by letting Firestore push changes straight to a React interface.
Every full-stack quiz run where you choose a real-time app (chat, live feed) ends here - regardless of experience, goal, hosting or team size. Real-time sync is the requirement that most narrows the field, so it decides the answer.
This isn't a classic three-tier stack where every request goes through your server:
That's why this stack works even on shared hosting: the built React app is static files, and the real-time part runs on Google's servers.
import { useEffect, useState } from "react";
import { collection, query, orderBy, limitToLast, onSnapshot } from "firebase/firestore";
import { db } from "./firebase";
export function useMessages() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const q = query(collection(db, "messages"), orderBy("createdAt"), limitToLast(50));
const unsubscribe = onSnapshot(q, (snap) => {
setMessages(snap.docs.map((d) => ({ id: d.id, ...d.data() })));
});
return unsubscribe; // stop listening when the component unmounts
}, []);
return messages;
}
Any component that calls useMessages() now updates live whenever anyone sends a message.
The React app can live on Firebase Hosting, Netlify, Cloudflare Pages or any shared hosting plan. Firestore's free Spark plan covers learning projects and small apps. Note that deploying Cloud Functions requires the pay-as-you-go Blaze plan (it still includes a free allowance); if you'd rather avoid that, run your Node code on a separate host such as Render.
onSnapshot in a useEffect must return its unsubscribe function, or you'll leak listeners and pay for extra reads.