Every time a user visits a traditional server-rendered page, the server runs code, queries a database, and builds the HTML from scratch. JAMstack turns that around: build the HTML once at deploy time, serve the static files from a CDNWhat is cdn?Content Delivery Network - a network of servers around the world that caches your files and serves them from the location closest to the user, making pages load faster., and let client-side JavaScript handle anything dynamic. The result is pages that load instantly because the server has already done all the work.
Traditional vs JAMstack request flow
The fundamental difference is when the HTML is generated.
Traditional (every request):
User requests page
→ Server processes request
→ Database query runs
→ HTML is rendered
→ Response sent (300-2000ms)
JAMstack (every deploy):
Build time: fetch data → generate HTML → upload to CDN
Request time: user requests page → CDN serves file instantly (~50ms)Serving a pre-built file from a CDNWhat is cdn?Content Delivery Network - a network of servers around the world that caches your files and serves them from the location closest to the user, making pages load faster. is orders of magnitude faster than running server code on every request.
The three parts of JAMstack
JavaScript handles dynamic behavior after the initial page load, fetching comments, handling form submissions, updating state in the browser.
APIs provide the backend, serverlessWhat is serverless?A hosting model where individual functions run on demand and the platform handles all server management, scaling, and uptime for you. functions, third-party services (Stripe, Auth0, Algolia), or your own RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources./GraphQLWhat is graphql?A query language for APIs where clients specify the exact shape of data they need in a single request, avoiding over-fetching and under-fetching. APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses..
Markup is the pre-built HTML. A static site generator fetches your content at build time and outputs HTML files ready for the CDNWhat is cdn?Content Delivery Network - a network of servers around the world that caches your files and serves them from the location closest to the user, making pages load faster..
Static site generators
The generator is what turns your content into HTML files at build time.
// Next.js: getStaticProps runs at BUILD TIME, not request time
export async function getStaticProps() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return {
props: { posts },
revalidate: 3600 // ISR: re-generate after 1 hour
};
}
export default function Home({ posts }) {
return (
<div>
{posts.map(post => <article key={post.id}>{post.title}</article>)}
</div>
);
}---
// Astro: data fetched at build time
const posts = await fetch('/api/posts').then(r => r.json());
---
<ul>
{posts.map(post => <li>{post.title}</li>)}
</ul>Tool ecosystem
| Category | Options | Notes |
|---|---|---|
| Static generators | Next.js, Astro, Gatsby, Hugo, 11ty | Next.js most popular for React devs |
| Headless CMS | Contentful, Sanity, Strapi | Manage content, expose via API |
| Hosting/CDN | Vercel, Netlify, Cloudflare Pages | Deploy with git push |
| Dynamic features | Serverless functions, Auth0, Algolia | Bolt on what you need |
Adding dynamic features
Static pages don't mean static experiences. You add interactivity by fetching data client-side after the initial page load.
function BlogPost({ post }) {
const [comments, setComments] = useState([]);
useEffect(() => {
// Static page, but dynamic comments loaded client-side
fetch(`/api/comments?post=${post.id}`)
.then(r => r.json())
.then(setComments);
}, [post.id]);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
<section>
{comments.map(c => <p key={c.id}>{c.text}</p>)}
</section>
</article>
);
}Common third-party services for dynamic JAMstack features: Disqus or Utterances for comments, Formspree or Netlify Forms for contact forms, Algolia for search, Auth0 or Supabase for authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token..
Incremental Static RegenerationWhat is isr?Incremental Static Regeneration - a feature that rebuilds individual static pages in the background after a set time, giving you CDN speed with reasonably fresh data.
Re-building your entire site every time a blog post changes is impractical for large sites. ISR lets Next.js re-generate only the pages that have changed, in the background, after deployment.
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 60 // Re-generate this page after 60 seconds if requested
};
}When JAMstack fits and when it doesn't
| Good fit | Poor fit |
|---|---|
| Blogs and documentation | Social networks with per-user feeds |
| Marketing and landing pages | Real-time collaborative tools |
| Portfolios and company sites | Apps that update data thousands of times per day |
| E-commerce product pages | Highly personalized per-user content |
| Developer docs | Complex form-heavy internal apps |
// JAMstack Example: Blog with Next.js
// pages/index.js
export async function getStaticProps() {
// Runs at BUILD time (not request time)
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json());
return {
props: { posts },
revalidate: 3600 // ISR: Regenerate every hour
};
}
export default function Home({ posts }) {
return (
<div>
<h1>My Blog</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
// pages/blog/[slug].js
export async function getStaticPaths() {
// Tell Next.js which pages to pre-build
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json());
const paths = posts.map(post => ({
params: { slug: post.slug }
}));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(r => r.json());
return { props: { post } };
}
export default function BlogPost({ post }) {
const [comments, setComments] = useState([]);
// Dynamic comments (client-side)
useEffect(() => {
fetch(`/api/comments?post=${post.id}`)
.then(r => r.json())
.then(setComments);
}, [post.id]);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
<section>
<h2>Comments</h2>
{comments.map(c => (
<div key={c.id}>{c.text}</div>
))}
</section>
</article>
);
}
// pages/api/comments.js (Serverless function)
export default async function handler(req, res) {
const postId = req.query.post;
const comments = await database.comments.find({ postId });
res.json(comments);
}
// Deploy:
// 1. git push
// 2. Vercel auto-builds and deploys
// 3. Site live on CDN worldwide!