ISR with Databases — Fetching Database Content with ISR
In this tutorial, you will learn about ISR with Databases. We cover key concepts, practical examples, and best practices to help you master this topic.
ISR with databases queries database content during page generation and caches the rendered HTML, balancing live data with static performance.
What You'll Learn
By the end of this tutorial, you'll understand how to connect databases to ISR pages, manage database connections during build and revalidation, handle connection pooling, and optimize queries for ISR performance.
Why It Matters
Static sites are fast but databases are dynamic. ISR bridges this gap by querying the database during page generation, then caching the result as static HTML. Users get database-powered content at CDN speeds.
Real-World Use
A product catalog uses PostgreSQL for inventory data. ISR pages query the database during revalidation, rendering product pages with current prices and stock levels. The database handles only revalidation requests, not every user visit.
ISR Database Architecture
graph TD
A[ISR Revalidation] --> B[Connect to
Database]
B --> C[Execute Query
fetch product data]
C --> D[Render Page
with data]
D --> E[Store HTML
in cache]
E --> F[Close DB
Connection]
F --> G[Serve cached
HTML to users]
G --> H[Only revalidations
hit the database]
style B fill:#4a90d9,color:#fff
style C fill:#e67e22,color:#fff
style E fill:#27ae60,color:#fff
style H fill:#f39c12,color:#fff
PostgreSQL with ISR
// lib/db.js — Database connection pool
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // Max connections in pool
idleTimeoutMillis: 30000, // Close idle connections after 30s
connectionTimeoutMillis: 5000, // Fail fast if DB is down
});
export async function query(text, params) {
const client = await pool.connect();
try {
const result = await client.query(text, params);
return result.rows;
} catch (err) {
console.error('Database query error:', err);
throw err;
} finally {
client.release();
}
}
// pages/products/[id].js — ISR with PostgreSQL
export async function getStaticProps({ params }) {
const start = Date.now();
try {
const products = await query(
'SELECT id, name, price, description, stock, category_id, image_url, updated_at FROM products WHERE id = $1 AND active = true',
[params.id]
);
if (products.length === 0) {
return { notFound: true };
}
const product = products[0];
// Also fetch category name
const categories = await query(
'SELECT name FROM categories WHERE id = $1',
[product.category_id]
);
console.log(`DB query for product ${params.id}: ${Date.now() - start}ms`);
return {
props: {
product: {
...product,
category: categories[0]?.name || 'Uncategorized'
},
generatedAt: Date.now()
},
revalidate: 60
};
} catch (err) {
console.error('Failed to fetch product:', err);
return { notFound: true };
}
}
MongoDB with ISR
// lib/mongodb.js — MongoDB connection for ISR
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 10000,
});
let cachedClient = null;
let cachedDb = null;
export async function connectToDatabase() {
if (cachedClient && cachedDb) {
return { client: cachedClient, db: cachedDb };
}
const client = await MongoClient.connect(uri, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
});
const db = client.db(process.env.MONGODB_DB || 'myapp');
cachedClient = client;
cachedDb = db;
return { client, db };
}
// pages/blog/[slug].js — ISR with MongoDB
export async function getStaticProps({ params }) {
const start = Date.now();
try {
const { db } = await connectToDatabase();
const collection = db.collection('posts');
const post = await collection.findOne({
slug: params.slug,
status: 'published'
});
if (!post) {
return { notFound: true };
}
console.log(`MongoDB query for ${params.slug}: ${Date.now() - start}ms`);
return {
props: {
post: {
id: post._id.toString(),
title: post.title,
content: post.content,
slug: post.slug,
author: post.author,
publishedAt: post.publishedAt?.toISOString(),
tags: post.tags || []
},
generatedAt: Date.now()
},
revalidate: 300
};
} catch (err) {
console.error('MongoDB query failed:', err);
return { notFound: true };
}
}
SQLite with ISR
// lib/sqlite.js — SQLite for ISR (local/edge databases)
const Database = require('better-sqlite3');
const path = require('path');
const DB_PATH = path.join(process.cwd(), 'data', 'content.db');
let db = null;
function getDatabase() {
if (!db) {
db = new Database(DB_PATH, {
readonly: true, // ISR only reads — writes via separate process
fileMustExist: true
});
db.pragma('journal_mode = WAL');
}
return db;
}
// pages/docs/[slug].js — ISR with SQLite
export async function getStaticProps({ params }) {
try {
const database = getDatabase();
const stmt = database.prepare(`
SELECT d.*, c.name as category_name
FROM documents d
LEFT JOIN categories c ON d.category_id = c.id
WHERE d.slug = ? AND d.status = 'published'
`);
const doc = stmt.get(params.slug);
if (!doc) {
return { notFound: true };
}
return {
props: {
doc: {
...doc,
created_at: doc.created_at?.toISOString(),
updated_at: doc.updated_at?.toISOString()
},
generatedAt: Date.now()
},
revalidate: 3600
};
} catch (err) {
console.error('SQLite query failed:', err);
return { notFound: true };
}
}
Database Connection Best Practices
// lib/db-best-practices.js — ISR database patterns
const dbBestPractices = {
connectionPool: {
why: 'ISR functions may run on multiple serverless instances simultaneously',
practice: 'Use a connection pool (max 5-10 connections)',
config: 'max: 10, idleTimeoutMillis: 30000'
},
queryTimeout: {
why: 'ISR revalidation has a 60-second timeout on Vercel',
practice: 'Set query timeouts (5-10 seconds max)',
config: 'statement_timeout: 5000'
},
readReplicas: {
why: 'ISR only reads data — use read replicas to avoid production DB load',
practice: 'Configure a separate read-only database connection',
config: 'DATABASE_URL_READONLY environment variable'
},
caching: {
why: 'ISR already caches HTML, but frequent revalidations still query DB',
practice: 'Add an in-memory query cache layer',
config: 'Use node-cache or Redis for query results'
},
errorHandling: {
why: 'DB failures during revalidation keep stale content',
practice: 'Return fallback data instead of crashing',
config: 'try-catch with fallback return'
},
monitoring: {
why: 'Track database performance during revalidation',
practice: 'Log query duration and error rates',
config: 'console.time() + structured logging'
}
};
Common Mistakes
- Opening a new connection for every revalidation. Serverless functions may create many connections. Use a Connection Pool and reuse connections across revalidations.
- Not setting query timeouts. A slow query blocks the entire revalidation. Set statement_timeout to Fail Fast. The stale cache continues serving.
- Querying the production database directly. ISR revalidations add database load. Use read replicas or a dedicated reporting database.
- Including sensitive data in ISR props. Database records may contain passwords or internal fields. Select only the fields needed by the page.
- Not handling database failures gracefully. If the database is down, ISR keeps serving stale content. But the revalidation error should be logged and monitored.
Practice Questions
- How do you manage database connections for ISR in serverless environments?
- Why should you use read replicas for ISR database queries?
- How do you handle database query timeouts during revalidation?
- What is the impact of database queries on ISR revalidation time?
- How do you ensure sensitive database fields aren't exposed through ISR props?
Challenge: Build an ISR-powered product catalog with PostgreSQL: create a database schema for products and categories, implement connection pooling, add query timeout handling, and benchmark ISR revalidation time with database queries vs cached responses.
FAQ
Mini Project
Create a database-backed ISR site: set up PostgreSQL with a products table (50+ records), implement connection pooling with proper timeouts, create ISR pages that query the database during revalidation, add query performance monitoring, and benchmark against a cached version.
What's Next
You've integrated databases with ISR. Now explore ISR with Headless CMS for Webhook-triggered revalidation from CMS content updates.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro