Skip to content

ISR with Databases — Fetching Database Content with ISR

DodaTech Updated 2026-06-28 6 min read

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

  1. Opening a new connection for every revalidation. Serverless functions may create many connections. Use a Connection Pool and reuse connections across revalidations.
  2. Not setting query timeouts. A slow query blocks the entire revalidation. Set statement_timeout to Fail Fast. The stale cache continues serving.
  3. Querying the production database directly. ISR revalidations add database load. Use read replicas or a dedicated reporting database.
  4. Including sensitive data in ISR props. Database records may contain passwords or internal fields. Select only the fields needed by the page.
  5. 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

  1. How do you manage database connections for ISR in serverless environments?
  2. Why should you use read replicas for ISR database queries?
  3. How do you handle database query timeouts during revalidation?
  4. What is the impact of database queries on ISR revalidation time?
  5. 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

Can ISR use an ORM like Prisma or TypeORM?

Yes. ORMs work with ISR. Be mindful of connection management in serverless. Prisma has built-in connection pooling support.

How does database connection pooling work with serverless ISR?

Serverless functions may have cold starts. Use a connection pooler (PgBouncer) or serverless DB adapters that handle ephemeral connections.

What happens if the database is unavailable during revalidation?

The revalidation fails, but the cached page continues serving. The next request within the revalidate window will trigger another attempt.

Should I use a CMS or direct database queries for ISR?

A CMS adds a management layer. Direct DB queries give more control and performance. Choose based on whether content editors need a UI.

How do I monitor database performance during ISR revalidations?

Instrument queries with timing logs, set up error tracking (Sentry, DataDog), and monitor slow queries with EXPLAIN ANALYZE.

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