Bitdoze Logo

Astro Build Speed Optimization: 35 to 127 Pages/Second

Astro build speed optimization for large SSG sites: 8 beginner-friendly steps took a 339k-page site from 35 to 127 pages per second. No SSR switch needed.

DragosDragos30 min read
Astro Build Speed Optimization: 35 to 127 Pages/Second

Slow Astro builds? People keep telling you to “just switch to SSR” for your large site? This guide to Astro build speed optimization covers how to speed up Astro builds while sticking with Static Site Generation (SSG), which makes more sense for most large sites.

The techniques here improved a large SSG site from 35 pages/second to 127 pages/second. That’s a 3.6x speed improvement that cut build time from 9,642 seconds (2.7 hours) down to 2,659 seconds (44 minutes). These work for beginners and don’t require switching to SSR.

This article comes from a Reddit case study: Astro build speed optimization from 9642s to 2659s. If you’re migrating a large content site from WordPress to Astro, these optimization steps will save you hours of build time.

What you’ll get from this guide:

  • Measurable build speed improvements (tested on 339k+ pages)
  • Copy-pasteable config snippets you can apply tonight
  • Troubleshooting for OOM errors, slow APIs, and inconsistent build times

Before we look at the steps, here are some related Astro articles:

Understanding SSG vs SSR

The difference between Static Site Generation (SSG) and Server-Side Rendering (SSR) matters when choosing an approach for your project.

Static Site Generation (SSG)

Pages are pre-built at build time and served as static HTML files.

How it works:

  1. During build, Astro processes your content and components
  2. Generates static HTML files for each page
  3. These files are served directly by a CDN or web server
  4. No server processing needed for each request

Pros:

  • Fast delivery. Files served directly from CDN.
  • Cost-effective. Minimal server resources needed.
  • Resilient to traffic spikes. Static files scale without extra compute.
  • More secure. No server-side vulnerabilities.
  • Good SEO. Search engines index static content well.

Cons:

  • Build time grows with more pages
  • Data freshness depends on rebuild frequency
  • Limited personalization without JavaScript

Server-Side Rendering (SSR)

Pages are generated on each request (or cached with smart rules).

How it works:

  1. User requests a page
  2. Server processes the request in real-time
  3. Generates HTML dynamically
  4. Sends response to user

Pros:

  • Always fresh data. Content is up-to-date on every request.
  • Full personalization. Can customize per user/request.
  • Fast time-to-first-page. No build step needed.

Cons:

  • Higher costs. Requires server capacity for each request.
  • Slower under load. Server processing needed for every request.
  • Vulnerable to crawler load. Bots can overwhelm your server.

Why SSG Often Beats SSR at Scale

The key insight from the case study: “You don’t ever fear a single item getting a million views in a day, you fear 100,000 items getting 10 views in a day.”

The spider problem

Modern websites face heavy crawler load:

  • Search engine bots (Google, Bing, etc.)
  • AI training scrapers (ChatGPT, Claude, Perplexity, etc.)
  • SEO tools and monitoring services
  • Scraper bots

Real numbers from the case study:

  • 2.3 million requests per day
  • 774,860 unique visitors
  • 710k unique URLs requested
  • 30:1 ratio of spider traffic to human traffic

With SSG, each of these requests is a cheap file serve. With SSR, each request requires server processing power.

AI Crawlers Are Growing

AI scrapers from ChatGPT, Claude, Perplexity, and others are increasing spider load on websites every month. If you’re running SSR, this traffic multiplies your server costs. You can block AI crawlers and reduce spider load with proper rules. To measure the actual impact on your site, load-test your site with oha to see crawler vs human traffic patterns.

What it costs

SSG Setup (from case study):

  • $29 web server + memcached + workers
  • $29 database server
  • $89 build server
  • Total: $147/month

This setup handles 2.3M daily requests easily, with average load under 2 on an 8-core system.

Equivalent SSR Setup:

  • Would need multiple high-powered application servers
  • Database connection pooling and caching layers
  • Load balancers and auto-scaling
  • Estimated cost: $500-2,000+/month

Real-world performance case study

Here’s the actual optimization journey from the Reddit post:

Site Stats

  • 349,734 total files
  • 346,236 HTML pages
  • 43GB total size
  • API-powered build (no local .md files)

Performance Journey

Stage Pages Built Build Time Speed Improvement
Initial 339,194 9,642s (2.7 hours) ~35 pages/sec Baseline
Mid-optimization 339,251 3,583s (1 hour) ~94 pages/sec 2.7x faster
Final optimized 339,340 2,659s (44 minutes) ~127 pages/sec 3.6x faster

Case Study Source

These numbers come from a real production site documented in the original Reddit thread. The site pulls all content from APIs, no local markdown files, which makes the caching and concurrency optimizations especially impactful.

Now let’s break down exactly how they achieved this improvement.

Prerequisites

Before you start optimizing, make sure you have these in place:

  • Astro 7+ project (or willingness to upgrade)
  • Node.js ≥ 22.12 LTS — install Node.js with NVM if you haven’t already
  • npm, pnpm, or bun installed
  • Access to the astro.config.mjs file
  • At least 8GB RAM on the build machine (16GB+ recommended for 10k+ pages)
  • SSH access to the build server (if not building locally)
  • A way to time builds — the time command or the build-metrics script provided later

8 steps to optimize your Astro builds

These eight steps speed up Astro builds without touching your content. They’re ordered by impact-to-effort ratio. Do them in sequence for the best results.

Step 1: Upgrade Node.js and Astro

This is the highest-impact quick win. Astro 7 ships with a Rust-based .astro compiler and Vite 8 with Rolldown. Both deliver measurable build speed improvements over previous versions.

Astro 7 already cuts build times in half on a 743-page site with the Rust compiler alone. Combined with Node.js 22+ (which improves V8 garbage collection and memory handling), you get a significant baseline improvement.

# Check current versions
node --version
npm list astro

# Upgrade Node.js to latest LTS (22.12+)
nvm install 22
nvm use 22

# Upgrade Astro to latest 7.x
npm update astro

# If npm update fails with peer dependency conflicts:
npx @astrojs/upgrade

Verify: Run node --version (should show ≥ 22.12) and npm list astro (should show 7.x).

Failure mode: If npm update astro fails with peer dependency conflicts, use npx @astrojs/upgrade. Astro’s official upgrade tool handles migrations cleanly.

If package install time is your bottleneck, consider migrating Astro to Bun for faster installs.

Expected improvement: ~30% faster builds from version improvements alone.

Step 2: Increase Node.js memory allocation

Large builds can hit memory limits, causing garbage collection pauses and slowdowns. When Node.js runs low on heap memory, V8’s mark-compact garbage collector kicks in and stalls the entire build.

# Method 1: Environment variable (recommended)
export NODE_OPTIONS="--max-old-space-size=8192"

# Method 2: Direct command
node --max-old-space-size=8192 ./node_modules/.bin/astro build

Memory allocation guide:

  • Small sites (< 1k pages): 4GB (4096)
  • Medium sites (1k-10k pages): 8GB (8192)
  • Large sites (10k+ pages): 16GB+ (16384)

Verify: After a build, run node -e "console.log(process.memoryUsage())" to check heap usage. It should be well under your allocated limit.

Failure mode: If you see “FATAL ERROR: Ineffective mark-compacts near heap limit”, you need more memory or lower concurrency. See the Troubleshooting section below.

Expected improvement: Reduced build time and eliminated memory-related crashes.

Step 3: Optimize build concurrency

Astro can process multiple pages simultaneously, but too much concurrency causes resource contention. The sweet spot depends on your CPU core count.

// astro.config.mjs
export default defineConfig({
  build: {
    concurrency: 4, // Start here, then test 2, 6, 8
  },
});

Testing methodology:

  1. Start with concurrency: 2
  2. Run a build and time it
  3. Increase to 4, then 6, then 8
  4. Use the fastest setting

Verify: Run builds at concurrency 2, 4, 6, and 8. Record the times and pick the fastest. The case study found 4 was optimal on a 12-core system. More isn’t always better.

Failure mode: If builds hang or crash at high concurrency, reduce to 2 and work up. On shared CI runners, start at 2.

Step 4: Configure Vite and Rollup for speed

Astro 7 + Vite 8 Note

Astro 7 ships with Vite 8 and Rolldown under the hood. The config below works with both Rollup and Rolldown, but some Vite-specific keys may behave differently. Check the Astro 7 docs for migration notes if you’re upgrading from Astro 6.

Vite handles bundling and optimization. Proper configuration can improve build speed.

Here’s the optimized configuration from the case study:

// astro.config.mjs
import { defineConfig } from "astro/config";
import { readFileSync } from "fs";
import { cpus } from "os";

const packageJson = JSON.parse(readFileSync("./package.json", "utf8"));
const CPU_COUNT = cpus().length;

export default defineConfig({
  build: {
    // Optimize concurrency for your CPU
    concurrency: 4,

    rollupOptions: {
      // Maximum parallel file operations
      maxParallelFileOps: CPU_COUNT * 3,
      output: {
        // Fewer, larger chunks = less overhead
        manualChunks: undefined,
        // Faster code generation
        generatedCode: {
          preset: 'es2022'
        }
      }
    }
  },

  vite: {
    build: {
      // Allow larger chunks for speed
      chunkSizeWarningLimit: 10000,
      // Fastest minifier
      minify: 'esbuild',
      // Less transformation needed
      target: 'es2022',
      rollupOptions: {
        maxParallelFileOps: CPU_COUNT * 3
      }
    },

    esbuild: {
      target: 'es2022',
      // Fast minification settings
      minifyIdentifiers: false, // Skip for speed
      minifySyntax: true,
      minifyWhitespace: true,
    },

    // Aggressive caching for faster subsequent builds
    optimizeDeps: {
      force: false // Use cache when possible
    }
  },

  // Skip HTML compression for faster builds
  compressHTML: false,
});

Key optimizations explained:

  • manualChunks: undefined reduces chunk fragmentation overhead
  • target: 'es2022' means less transpilation (modern target)
  • minify: 'esbuild' is the fastest minifier available
  • compressHTML: false skips compression for speed. Let your CDN handle compression instead; the output difference is negligible.
  • maxParallelFileOps utilizes all CPU cores efficiently

Verify: Run time npm run build before and after applying the config. Compare total build time and pages/sec.

Failure mode: If chunkSizeWarningLimit warnings appear, that’s informational — not a failure. If you see Rollup/Rolldown errors about manualChunks, check Astro 7 migration notes.

Step 5: Implement Smart Caching

Why this matters: If your site pulls data from APIs (which the case study site does for all 339k+ pages), caching eliminates redundant network requests. This is the single biggest win for API-powered SSG sites.

If your Astro site is powered by a headless CMS, smart caching is the most impactful optimization you can apply.

Here’s a robust caching implementation:

// utils/fetchWithCache.js
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';

export async function fetchWithCache(url, expirationSeconds = 600) {
  const start = Date.now();

  // Create unique cache filename
  const urlHash = crypto.createHash('md5').update("cache_v1_" + url).digest('hex');
  const cacheDir = path.join(process.cwd(), '.cache');
  const cacheFile = path.join(cacheDir, `${urlHash}.json`);

  // Ensure cache directory exists
  if (!fs.existsSync(cacheDir)) {
    fs.mkdirSync(cacheDir, { recursive: true });
  }

  // Check if cache file exists and is fresh
  if (fs.existsSync(cacheFile)) {
    const stats = fs.statSync(cacheFile);
    const ageInSeconds = (Date.now() - stats.mtime.getTime()) / 1000;

    if (ageInSeconds < expirationSeconds) {
      const cachedData = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
      console.log(`Cache hit: ${url} (${ageInSeconds.toFixed(1)}s old)`);
      return cachedData;
    }
  }

  // Fetch fresh data
  console.log(`Fetching: ${url}`);
  const response = await fetch(url, {
    headers: {
      'User-Agent': 'Astro Build Bot',
    },
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  const data = await response.json();

  // Save to cache
  fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));

  console.log(`Fresh fetch completed: ${((Date.now() - start) / 1000).toFixed(2)}s`);
  return data;
}

Usage in your Astro pages:

// pages/[...slug].astro
---
import { fetchWithCache } from '../utils/fetchWithCache.js';

export async function getStaticPaths() {
  // Use cached fetch instead of regular fetch
  const posts = await fetchWithCache('https://api.example.com/posts');

  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post }
  }));
}
---

Verify: After the first build with caching, check that the .cache/ directory exists and contains JSON files. The second build should be significantly faster — look for “Cache hit” messages in the console.

Failure mode: If content looks outdated, rm -rf .cache and rebuild. Set appropriate expirationSeconds — 600 (10 minutes) is a good default.

Step 6: Cache Prewarming (Advanced)

Why this matters: For very large sites with many API endpoints, you can prewarm your cache before the main build starts. Skip this step if you have a small site or fewer than 5 API endpoints — the basic caching in Step 5 handles that fine.

Here’s a Node.js cache prewarming script:

// scripts/prewarmCache.js
import { fetchWithCache } from '../utils/fetchWithCache.js';

async function prewarmCache() {
  console.log('Starting cache prewarming...');

  // Define your API endpoints to prewarm
  const endpoints = [
    'https://api.example.com/posts',
    'https://api.example.com/categories',
    'https://api.example.com/authors',
    // Add more endpoints as needed
  ];

  // Warm cache with limited concurrency
  const results = await Promise.allSettled(
    endpoints.map(url => fetchWithCache(url, 3600)) // 1 hour cache
  );

  const successful = results.filter(r => r.status === 'fulfilled').length;
  console.log(`Cache prewarming complete: ${successful}/${endpoints.length} successful`);
}

prewarmCache().catch(console.error);

Run before your main build:

// package.json scripts
{
  "scripts": {
    "prewarm": "node scripts/prewarmCache.js",
    "build": "npm run prewarm && astro build"
  }
}

Verify: Run npm run prewarm separately and check console output for “Cache prewarming complete: X/Y successful.”

Failure mode: If prewarm fails on some endpoints, check API availability and rate limits. The script uses Promise.allSettled so partial failures don’t block the build.

Step 7: Consider Ramdisk (Conditional)

NVMe Users: Skip This Step

If you have NVMe storage, ramdisk provides less than 1% improvement. This step is only useful for HDD or old SATA SSDs. Modern NVMe drives are fast enough that the setup overhead isn’t worth it.

When it helps: Only with slow storage (spinning disks, old SSDs).

When it doesn’t help: Modern NVMe drives — improvement is typically less than 1%.

How to set up (Linux/macOS):

# Create 4GB ramdisk
sudo mount -t tmpfs -o size=4g tmpfs /tmp/astro-build

# Build in ramdisk
cd /tmp/astro-build
# ... run your build here ...

Verify: Run df -h /tmp/astro-build to confirm the ramdisk is mounted and sized correctly.

Failure mode: If build exceeds ramdisk size, you’ll get “No space left on device.” Size the ramdisk to at least 2x your expected output size.

Step 8: Hardware Upgrades

When it’s worth it: Only if you’re building multiple times per day and you’ve already done steps 1-7. Hardware upgrades have real ROI but they’re the last lever to pull.

What matters most:

  1. CPU single-core performance — Node.js loves fast cores
  2. CPU cache (L3/L4) — More cache = faster builds
  3. Fast storage — NVMe > SATA SSD > HDD
  4. Adequate RAM — Avoid swapping at all costs

Case study hardware impact:

  • Old: Intel Xeon E5-1650 v3 → 3,583s build time
  • New: AMD Ryzen 9 5900X → 2,659s build time
  • 25% improvement from CPU upgrade alone

Verify: Run the same build on the new hardware and compare to your baseline.

Advanced Caching Strategies

Once you have basic caching (Step 5) working, these strategies help with multi-API sites, frequent content updates, and CI/CD builds.

Cache Invalidation Strategy

Smart cache invalidation ensures fresh data when needed:

// utils/smartCache.js
export async function fetchWithSmartCache(url, options = {}) {
  const {
    maxAge = 600,
    forceRefresh = false,
    invalidateOn = []
  } = options;

  if (forceRefresh) {
    return await fetchFresh(url);
  }

  // Check for invalidation conditions
  for (const condition of invalidateOn) {
    if (await condition()) {
      console.log(`Cache invalidated for ${url}`);
      return await fetchFresh(url);
    }
  }

  return await fetchWithCache(url, maxAge);
}

// Usage with invalidation
const posts = await fetchWithSmartCache('https://api.example.com/posts', {
  maxAge: 3600, // 1 hour
  invalidateOn: [
    () => process.env.FORCE_REFRESH === 'true',
    () => Date.now() - lastDeployTime < 300000 // 5 minutes after deploy
  ]
});

Batch Request Optimization

Minimize API calls by batching requests:

// utils/batchFetch.js
export async function batchFetchWithCache(urls, batchSize = 10) {
  const results = [];

  for (let i = 0; i < urls.length; i += batchSize) {
    const batch = urls.slice(i, i + batchSize);

    const batchResults = await Promise.allSettled(
      batch.map(url => fetchWithCache(url))
    );

    results.push(...batchResults);

    // Small delay to be nice to the API
    if (i + batchSize < urls.length) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }

  return results;
}

Verify: Add console.log(\Batch ${i}/${urls.length} complete`)` inside the loop to monitor progress during large builds.

Hardware Considerations

Before upgrading hardware, benchmark your cloud server to identify the actual bottleneck — CPU, RAM, or disk I/O. No point buying more RAM if your builds are CPU-bound.

CPU Requirements

Site Size Recommended CPU Cores Cache
Small (< 1k pages) Any modern CPU 4+ 8MB+
Medium (1k-10k pages) Intel i7/AMD Ryzen 7 8+ 16MB+
Large (10k+ pages) Intel i9/AMD Ryzen 9 12+ 32MB+
Huge (100k+ pages) Server-grade CPU 16+ 64MB+

Memory Requirements

Base calculation: ~2-4MB per page in memory during build.

Site Size Minimum RAM Recommended
Small (< 1k pages) 8GB 16GB
Medium (1k-10k pages) 16GB 32GB
Large (10k+ pages) 32GB 64GB
Huge (100k+ pages) 64GB 128GB+

Storage Considerations

Speed hierarchy:

  1. NVMe Gen4 — Best for large builds
  2. NVMe Gen3 — Great for most uses
  3. SATA SSD — Minimum recommended
  4. HDD — Only with ramdisk

Build Server Recommendations

For affordable build servers with excellent single-core performance, Hetzner Cloud offers dedicated CPU instances starting at ~€8/month. The CCX line (dedicated AMD EPYC) is ideal for Astro builds — you get predictable performance without noisy neighbors. Vultr is another option if you need global regions for distributed CI/CD pipelines.

When to Choose SSG vs SSR

Sometimes SSR really is the right call. Here’s a decision framework.

If you’re still deciding on a framework, see Astro vs Next.js vs TanStack Start: which wins in 2026 for a detailed comparison.

Decision Matrix

Factor SSG SSR Hybrid
Content freshness Rebuild required Always fresh Mixed
Personalization Limited Full Per-route
Performance Excellent Variable Excellent
Cost at scale Very low High Medium
Crawler resilience Excellent Poor Good
Development complexity Simple Complex Medium

Use Case Recommendations

Choose SSG when:

  • Content doesn’t change frequently (minutes/hours)
  • Heavy anonymous/crawler traffic expected
  • Budget constraints are important
  • Maximum performance is priority
  • Simple deployment preferred

Choose SSR when:

  • Real-time data is essential
  • Heavy personalization needed
  • User-generated content is primary
  • Small number of pages
  • Server resources aren’t constrained

Choose Hybrid when:

  • Most content is static, some dynamic
  • Need personalization on some routes
  • Want to optimize costs and performance
  • Can handle route-level complexity

Hybrid Implementation Example

// astro.config.mjs - Hybrid setup
export default defineConfig({
  output: 'hybrid',
  adapter: vercel(),

  integrations: [
    // Most pages are pre-rendered (SSG)
    // Specific routes can opt into SSR
  ],
});
// pages/dashboard/[user].astro - SSR route
---
export const prerender = false; // This page uses SSR

const { user } = Astro.params;
const userData = await fetch(`/api/user/${user}`);
---

<Layout title="Dashboard">
  <UserDashboard data={userData} />
</Layout>

Common Optimization Myths

Myth 1: "More concurrency is always better"

Reality: Concurrency has diminishing returns and can cause resource contention. More concurrent page builds means more memory pressure and more CPU contention.

Test this: Try concurrency values of 2, 4, 6, 8, 16. Most sites perform best between 2-6. The case study found 4 was optimal on a 12-core system.

Myth 2: "Ramdisk always speeds up builds"

Reality: Only helps with slow storage. NVMe drives make ramdisk nearly useless — the improvement is typically less than 1%.

Test this: Time your build with and without ramdisk on your storage setup. If you’re on NVMe, skip the ramdisk entirely.

Myth 3: "You need SSR for large sites"

Reality: SSG can handle hundreds of thousands of pages efficiently with proper optimization. The case study site has 339k+ pages and builds in under 45 minutes.

Evidence: The 30:1 spider-to-human traffic ratio means SSG’s cheap file serving saves far more money than SSR’s per-request flexibility.

Myth 4: "Build time doesn't matter in production"

Reality: Faster Astro build time means quicker deployments, more frequent content updates, lower CI/CD costs, and a better developer experience. If your build takes 2.7 hours, you’re deploying less often and reacting slower to content changes.

Myth 5: "HTML compression always saves significant space"

Reality: Modern CDNs handle compression (gzip, brotli) better than build-time compression. Astro’s compressHTML: true slows builds significantly for marginal output size savings. Let your CDN do the work.

Monitoring and Measuring Success

Track your Astro build time over time to catch regressions early. If your build suddenly gets slower, you’ll want to know exactly which dependency or config change caused it.

Build Performance Metrics

Track these metrics to measure optimization success:

// build-metrics.js
const startTime = Date.now();

export function logBuildMetrics(pageCount) {
  const buildTime = (Date.now() - startTime) / 1000;
  const pagesPerSecond = pageCount / buildTime;

  console.log(`
📊 Build Metrics:
- Pages built: ${pageCount.toLocaleString()}
- Build time: ${buildTime.toFixed(1)}s
- Speed: ${pagesPerSecond.toFixed(1)} pages/sec
- Memory usage: ${process.memoryUsage().heapUsed / 1024 / 1024:.1f}MB
  `);
}

CI/CD Integration

Track build performance over time in your CI pipeline:

# .github/workflows/build-monitor.yml
name: Build Performance Monitor
on: [push, pull_request]

jobs:
  build-perf:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - run: npm ci

      - name: Build with timing
        run: |
          echo "BUILD_START=$(date +%s)" >> $GITHUB_ENV
          npm run build
          echo "BUILD_END=$(date +%s)" >> $GITHUB_ENV

      - name: Report performance
        run: |
          BUILD_TIME=$((BUILD_END - BUILD_START))
          echo "Build completed in ${BUILD_TIME} seconds"
          # Send to your analytics/monitoring system

Verify: After deploying the monitoring workflow, check GitHub Actions logs for the “Build completed in X seconds” output on the next push.

Failure mode: If the GitHub Actions build is much slower than local, check the runner’s CPU/RAM specs — GitHub-hosted runners have 2 cores and 7GB RAM. For large sites, consider self-hosted runners.

Troubleshooting Common Issues

Quick Diagnostic

If your build is slow, check these in order: (1) Node.js version — are you on 22.12+? (2) Memory allocation — is --max-old-space-size high enough? (3) Concurrency setting — have you tested 2, 4, 6, 8? (4) Cache hit rate — is .cache/ populated? (5) API response times — are your endpoints responding fast?

Out of Memory Errors

Symptoms:

FATAL ERROR: Ineffective mark-compacts near heap limit
JavaScript heap out of memory

Solutions:

  1. Increase --max-old-space-size
  2. Reduce build concurrency
  3. Clear cache: rm -rf .cache node_modules/.vite
  4. Check for memory leaks in your code

Slow API Responses

Symptoms:

  • Build hangs on certain pages
  • Inconsistent build times
  • Network timeout errors

Solutions:

  1. Implement request timeout and retry logic
  2. Use caching aggressively
  3. Batch API requests when possible
  4. Consider API rate limiting
// Robust fetch with retries
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url, {
        timeout: 10000 // 10 second timeout
      });

      if (response.ok) return response;

      if (i === retries - 1) throw new Error(`HTTP ${response.status}`);

      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));

    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Inconsistent Build Times

Symptoms:

  • Build time varies significantly between runs
  • Some builds much slower than others

Solutions:

  1. Implement consistent caching strategy
  2. Check for system resource contention
  3. Monitor CPU/memory usage during builds
  4. Use fixed versions for all dependencies

Conclusion and Next Steps

Optimizing Astro builds for large SSG sites is entirely achievable. The core principle: SSG is about economic efficiency at scale. When crawlers and bots drive most of your traffic, serving pre-built files is far more cost-effective than processing every request server-side.

Quick wins — implement these first:

  • Upgrade Node.js (22.12+) and Astro (7.x)
  • Increase memory allocation to match your site size
  • Tune build concurrency (start with 4)
  • Configure Vite for speed (esbuild minifier, es2022 target)

Medium effort, high impact:

  • Implement smart caching for API calls
  • Optimize your astro.config.mjs with the full config from Step 4
  • Monitor and measure build performance over time

Advanced optimizations:

  • Cache prewarming for very large sites (100k+ pages)
  • Hardware upgrades if building frequently
  • Custom fetch implementations with retry logic
Deploy Your Optimized Astro Site to Cloudflare

Ready to learn more? Check out:

The full case study with detailed logs and configurations is available in the original Reddit thread.