SSG Build Performance — Optimizing Build Time for Large Static Sites
In this tutorial, you will learn about SSG Build Performance. We cover key concepts, practical examples, and best practices to help you master this topic.
SSG build performance optimization focuses on incremental builds, Caching, parallel processing, content filtering, and efficient data fetching to keep builds fast.
What You'll Learn
By the end of this tutorial, you'll understand what makes SSG builds slow, how to implement incremental builds, cache data fetching, parallelize processing, and monitor build performance.
Why It Matters
A site that takes 2 hours to build can't iterate quickly. Content updates take forever, developers lose productivity, and deployment pipelines bottleneck. Optimizing build time is essential for scaling SSG.
Real-World Use
A documentation site grew from 100 to 15,000 pages. Build time went from 30 seconds to 45 minutes. After implementing incremental builds, data caching, and parallel processing, build time dropped back to under 5 minutes.
Build Performance Bottlenecks
graph TD
A[Build Process] --> B[Data Fetching]
A --> C[Template Rendering]
A --> D[Asset Processing]
A --> E[File Writing]
B --> F[External API calls
Content fetching]
C --> G[HTML generation
Page rendering]
D --> H[Image optimization
CSS/JS bundling]
E --> I[Output file writing]
F --> J[Slowdown: API latency
Rate limits, large payloads]
G --> K[Slowdown: Many pages
Complex templates]
H --> L[Slowdown: Large images
Many variants]
I --> M[Slowdown: Many small files]
style J fill:#e74c3c,color:#fff
style K fill:#e74c3c,color:#fff
style L fill:#e74c3c,color:#fff
style M fill:#e74c3c,color:#fff
Data Caching
// scripts/build-with-cache.js — Cache API responses
const fs = require('fs');
const path = require('path');
const CACHE_DIR = './.build-cache';
function getCacheKey(url) {
return path.join(CACHE_DIR, Buffer.from(url).toString('base64'));
}
async function fetchWithCache(url, ttl = 3600000) {
const cacheKey = getCacheKey(url);
// Check cache
if (fs.existsSync(cacheKey)) {
const cached = JSON.parse(fs.readFileSync(cacheKey, 'utf8'));
if (Date.now() - cached.timestamp < ttl) {
console.log(` Cache HIT: ${url}`);
return cached.data;
}
console.log(` Cache STALE: ${url}`);
}
// Fetch fresh data
console.log(` Cache MISS: ${url}`);
const response = await fetch(url);
const data = await response.json();
// Store in cache
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
fs.writeFileSync(cacheKey, JSON.stringify({
timestamp: Date.now(),
data,
}));
return data;
}
// Usage in build
async function buildSite() {
const start = Date.now();
// Posts API - cached for 1 hour during development
const posts = await fetchWithCache('https://api.example.com/posts', 3600000);
// Categories API - cached for 6 hours
const categories = await fetchWithCache(
'https://api.example.com/categories',
21600000
);
console.log(`Build completed in ${(Date.now() - start) / 1000}s`);
}
Parallel Processing
// scripts/build-parallel.js — Parallel page generation
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const CONCURRENCY = 10; // Max parallel operations
// Queue-based parallel executor
async function parallelMap(items, fn, concurrency = CONCURRENCY) {
const results = [];
const queue = [...items];
async function worker() {
while (queue.length > 0) {
const item = queue.shift();
results.push(await fn(item));
}
}
const workers = Array.from({ length: concurrency }, worker);
await Promise.all(workers);
return results;
}
// Generate pages in parallel
async function generateAllPages(posts) {
const tasks = posts.map(post => async () => {
const html = await renderPage(post);
const outputPath = path.join('./public', `${post.slug}.html`);
await writeFile(outputPath, html);
return post.slug;
});
const generated = await parallelMap(posts, async (post) => {
const html = await renderPage(post);
const outputPath = path.join('./public', `${post.slug}.html`);
await writeFile(outputPath, html);
return post.slug;
});
console.log(`Generated ${generated.length} pages in parallel`);
return generated;
}
// Batch image processing
async function processImagesInParallel(images) {
const results = await parallelMap(images, async (imagePath) => {
const output = await optimizeImage(imagePath);
return output;
}, 4); // Limit image processing to 4 concurrent
return results;
}
Content Filtering
// gatsby-node.js — Skip draft and future content
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
// Only fetch published, non-draft content
const result = await graphql(`
query {
allMarkdownRemark(
filter: {
frontmatter: {
draft: { ne: true }
date: { lte: "now" }
}
}
sort: { frontmatter: { date: DESC } }
) {
edges {
node {
id
frontmatter {
slug
title
date
}
}
}
}
}
`);
// Create pages only for filtered content
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: `/blog/${node.frontmatter.slug}`,
component: path.resolve('./src/templates/blog-post.js'),
context: { id: node.id },
});
});
};
Build Monitoring
// scripts/build-monitor.js — Track build performance
class BuildMonitor {
constructor() {
this.marks = {};
this.measures = [];
this.startTime = Date.now();
}
mark(name) {
this.marks[name] = Date.now();
console.log(`[BUILD] ${name}: ${this.marks[name]}`);
}
measure(name, startMark, endMark) {
const start = this.marks[startMark];
const end = this.marks[endMark] || Date.now();
const duration = end - start;
this.measures.push({ name, duration });
console.log(`[PERF] ${name}: ${(duration / 1000).toFixed(2)}s`);
}
summary() {
const totalTime = Date.now() - this.startTime;
console.log('\n=== Build Performance Summary ===');
this.measures.forEach(m => {
const pct = ((m.duration / totalTime) * 100).toFixed(1);
console.log(` ${m.name}: ${(m.duration / 1000).toFixed(2)}s (${pct}%)`);
});
console.log(` TOTAL: ${(totalTime / 1000).toFixed(2)}s`);
console.log('================================\n');
}
}
// Usage in build script
const monitor = new BuildMonitor();
monitor.mark('fetch-start');
const data = await fetchData();
monitor.mark('fetch-end');
monitor.measure('Data fetching', 'fetch-start', 'fetch-end');
monitor.mark('render-start');
const pages = await renderPages(data);
monitor.mark('render-end');
monitor.measure('Page rendering', 'render-start', 'render-end');
monitor.mark('image-start');
await processImages();
monitor.mark('image-end');
monitor.measure('Image processing', 'image-start', 'image-end');
monitor.summary();
Common Mistakes
- Not caching API responses in development. Every dev server restart re-fetches all data. Cache to local files with a TTL to speed up development builds.
- Generating all pages in sequence. Page rendering is CPU-bound but data fetching is I/O-bound. Parallelize both independently.
- Processing all images at once without concurrency limits. Too many parallel Sharp operations can exhaust memory. Limit concurrent image processing to 4-8.
- Not filtering unpublished content. Drafts, future-dated posts, and hidden pages still consume build time. Filter them out early.
- Ignoring template complexity. Nested partials, heavy data queries, and complex loops slow rendering. Profile templates and simplify hot paths.
Practice Questions
- What are the three main bottlenecks in SSG build performance?
- How does API response caching speed up development builds?
- How does parallel page generation reduce total build time?
- Why should you filter draft and unpublished content during build?
- How do you measure and monitor build performance over time?
Challenge: Profile and optimize a slow SSG build: instrument the build Process with timing marks, identify the slowest phase, implement parallel processing and caching, and reduce total build time by at least 50%.
FAQ
Mini Project
Optimize a slow SSG build: create a build monitoring dashboard that times each phase, implement data caching with configurable TTLs, add parallel page generation with concurrency limits, filter draft content from the build, and benchmark before/after improvements.
What's Next
Build is fast. Now implement Incremental Builds to rebuild only changed pages for even faster iteration.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro