Introduction
Jamstack is an architecture pattern that decouples the frontend from the backend, enabling pre-rendered static pages combined with dynamic APIs. For startups, this means incredible performance, rock-solid security, and minimal infrastructure costs. This guide covers everything you need to build Jamstack applications.
Understanding Jamstack
The Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Jamstack Architecture โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ CDN (Global Edge) โ โ
โ โ Static Files + Serverless โ โ
โ โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ โ
โ โผ โผ โผ โ
โ โโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ JS โ โ APIs โ โ CMS โ โ
โ โ Reactโ โ Supabase โ โ Contentfulโ โ
โ โ Vue โ โ Stripe โ โ Sanity โ โ
โ โโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ
โ Static + Dynamic = Jamstack โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Benefits
# Why Jamstack works for startups
benefits:
performance:
- "Pre-rendered HTML - instant loads"
- "CDN distribution - global edge"
- "No server rendering - fast TTFB"
security:
- "No server to hack"
- "Static files = attack surface minimized"
- "APIs isolated"
scalability:
- "CDN handles any traffic"
- "No server bottlenecks"
- "Pay only for CDN usage"
developer_experience:
- "Git-based workflow"
- "Preview deployments"
- "Modern frontend frameworks"
Static Site Generation (SSG)
Next.js Static Export
// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPostSlugs } from '@/lib/api'
// Generate static pages at build time
export async function generateStaticParams() {
const posts = await getAllPostSlugs()
return posts.map((post) => ({ slug: post.slug }))
}
// This page is pre-rendered at build time
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
Incremental Static Regeneration (ISR)
// ISR: Update pages without rebuild
export const revalidate = 60 // Revalidate every 60 seconds
// Or with dynamic data
export default async function ProductPage({ params }: { params: { slug: string } }) {
const product = await fetch(`https://api.example.com/products/${params.slug}`, {
next: { revalidate: 60 } // Cache for 60 seconds
}).then(r => r.json())
return <ProductDetails product={product} />
}
Dynamic Content with API Routes
Serverless API Routes
// pages/api/orders.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
try {
// Verify auth
const session = await getSession({ req })
if (!session) {
return res.status(401).json({ error: 'Unauthorized' })
}
// Create order in database
const order = await createOrder({
userId: session.user.id,
items: req.body.items,
})
// Process payment (Stripe)
await processPayment(order)
res.status(200).json({ orderId: order.id })
} catch (error) {
res.status(500).json({ error: 'Failed to create order' })
}
}
Connecting to External APIs
// Using external APIs in static pages
// app/page.tsx
async function getData() {
// Fetch at build time (for SSG)
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // ISR: revalidate every hour
})
return res.json()
}
export default async function Page() {
const data = await getData()
return (
<main>
<h1>{data.title}</h1>
{data.items.map(item => (
<div key={item.id}>{item.name}</div>
))}
</main>
)
}
Headless CMS Integration
Contentful Example
// lib/contentful.ts
import { createClient } from 'contentful'
export const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
})
// Fetch all blog posts
export async function getBlogPosts() {
const entries = await client.getEntries({
content_type: 'blogPost',
order: ['-sys.createdAt'],
})
return entries.items.map((entry) => ({
id: entry.sys.id,
title: entry.fields.title,
slug: entry.fields.slug,
content: entry.fields.content,
publishDate: entry.fields.publishDate,
}))
}
// Fetch single post
export async function getBlogPost(slug: string) {
const entries = await client.getEntries({
content_type: 'blogPost',
'fields.slug': slug,
limit: 1,
})
if (!entries.items.length) return null
const entry = entries.items[0]
return {
id: entry.sys.id,
title: entry.fields.title,
slug: entry.fields.slug,
content: entry.fields.content,
}
}
Sanity CMS Example
// lib/sanity.ts
import { createClient } from 'next-sanity'
export const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: '2024-01-01',
useCdn: true,
})
// GROQ query
export async function getProducts() {
return client.fetch(`
*[_type == "product"] | order(name asc) {
_id,
name,
slug,
price,
"imageUrl": image.asset->url
}
`)
}
Deployment
Vercel Deployment
# Deploy to Vercel with one command
npm i -g vercel
vercel
# Or connect GitHub - automatic deployments
# Every push triggers a deployment
// vercel.json
{
"framework": "nextjs",
"buildCommand": "npm run build",
"outputDirectory": ".next",
"rewrites": [
{
"source": "/api/(.*)",
"destination": "/api/$1"
}
]
}
Netlify Deployment
# netlify.toml
[build]
command = "npm run build"
publish = ".next"
[[redirects]]
from = "/api/*"
to = "/api/:splat"
status = 200
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Performance Optimization
Image Optimization
// Next.js Image component
import Image from 'next/image'
export default function ProductImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={600}
// Automatic optimization:
// - WebP/AVIF conversion
// - Responsive sizes
// - Lazy loading
// - Blur placeholder
placeholder="blur"
blurDataURL={generateBlurPlaceholder(src)}
/>
)
}
Script Optimization
// Load third-party scripts efficiently
import Script from 'next/script'
export function Analytics() {
return (
<>
<Script
src="https://analytics.example.com/script.js"
strategy="lazyOnload" // Load after page is interactive
/>
<Script
id="chat-widget"
src="https://chat.example.com/widget.js"
strategy="afterInteractive" // Load after hydration
onLoad={() => console.log('Chat loaded')}
/>
</>
)
}
Cost Analysis
Monthly Costs
# Jamstack hosting costs (2025)
providers:
vercel:
free: "100GB bandwidth, 100 hours build"
pro: "$20/month (unlimited bandwidth)"
netlify:
free: "100GB bandwidth"
pro: "$19/month"
cloudflare_pages:
free: "Unlimited bandwidth!"
# Truly free for most startups
Example: 100K Monthly Visitors
# Cost breakdown for 100K visitors
scenario:
page_views: "300K/month"
avg_page_size: "500KB"
bandwidth: "150GB/month"
vercel_pro: "$20/month"
netlify_pro: "$19/month"
cloudflare_pages: "$0/month (free)"
Common Pitfalls
1. Too Much Client-Side Rendering
Wrong:
// Fetching everything on client
useEffect(() => {
fetch('/api/data').then(setData)
}, [])
Correct:
// Fetch at build time or server
export async function getStaticProps() {
const data = await fetchData()
return { props: { data } }
}
2. Not Using Edge Functions
Wrong:
// Standard serverless (cold starts)
export default function handler(req, res) {
// Slow first request
}
Correct:
// Edge function (instant cold start)
export const runtime = 'edge'
export default function handler(req) {
// Global distribution, instant response
}
Key Takeaways
- Jamstack = Static + APIs - Best of both worlds
- Pre-render everything possible - Use SSG/ISR
- CDN handles scale - Don’t worry about traffic
- Choose Vercel/Netlify - Great DX and free tiers
- Use headless CMS - Content management without complexity
External Resources
Platforms
- Vercel - Next.js hosting
- Netlify - Jamstack hosting
- Cloudflare Pages - Free CDN
CMS Options
- Contentful - Headless CMS
- Sanity - Headless CMS
- Strapi - Open-source CMS
Comments