---
title: "Datenbankabfragen in Astro SSG wiederverwenden"
description: "Erfahren Sie, wie Sie Ergebnisse von Datenbankabfragen während der Static Site Generation in Astro über mehrere Seiten hinweg wiederverwenden, um redundante Datenbankaufrufe zu vermeiden und die Build-Performance zu verbessern."
source: "https://www.launchfa.st/de/blog/optimize-database-calls-astro-ssg"
author: "Rishi Raj Jain"
created_at: 2025-11-29T12:00:00.000Z
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="Optimize Database Calls During SSG in Astro" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/optimize-database-calls-astro-ssg.png" />

Beim Erstellen statischer Sites mit Astro stehen Entwickler oft vor einer Herausforderung: Datenbankaufrufe während der Static Site Generation (SSG) zu managen. Wenn mehrere Seiten dieselben Daten aus Ihrer Datenbank benötigen, stellt sich die Frage: **Cached Astro die Abfrageergebnisse, oder führt es für jede Seite einen separaten Datenbankaufruf aus?**

Die kurze Antwort: **Astro cached Datenbankabfragen nicht automatisch über Seiten hinweg**. Wenn drei verschiedene Seiten (`pages/foo`, `pages/bar`, `pages/ipsum`) alle `db.select().from(tbl)` ausführen, macht Astro während des Build-Prozesses **drei separate Aufrufe** an Ihre Datenbank.

In dieser Anleitung lernen Sie, wie Sie einen Cache-Mechanismus implementieren, damit Ihre Datenbank während SSG nur einmal abgefragt wird, unabhängig davon, wie viele Seiten diese Daten benötigen. Dieser Ansatz kann Ihre Build-Zeiten deutlich verbessern und die Datenbanklast reduzieren.

## Voraussetzungen

Sie benötigen Folgendes:

- [Node.j 20](https://nodejs.org/en/blog/announcements/v20-release-announce) oder höher
- Ein bestehendes Astro-Projekt
- Eine Datenbankverbindung (MySQL, PostgreSQL oder eine beliebige Datenbank)

## Inhaltsverzeichnis

- [Das Problem verstehen](#understanding-the-problem)
- [Die Lösung: Ein Cache auf Modulebene](#the-solution-a-module-level-cache)
- [Schritt-für-Schritt-Implementierung](#step-by-step-implementation)
  - [Schritt 1: Ein Datenbankverbindungsmodul erstellen](#step-1-create-a-database-connection-module)
  - [Schritt 2: Einen Query-Cache-Helper implementieren](#step-2-implement-a-query-cache-helper)
  - [Schritt 3: Die gecachte Abfrage in Ihren Seiten verwenden](#step-3-use-the-cached-query-in-your-pages)
  - [Schritt 4: Die Optimierung verifizieren](#step-4-verify-the-optimization)
- [Fortgeschritten: Cache mit TTL für die Entwicklung](#advanced-cache-with-ttl-for-development)
- [So funktioniert es](#how-it-works)
- [Performance-Vorteile](#performance-benefits)

## Das Problem verstehen

Während der Static Site Generation baut Astro jede Seite unabhängig. Betrachten Sie dieses Szenario:

```astro
---
// File: src/pages/foo.astro
import { db } from '@/db'
const products = await db.select().from(productsTable)
---
<div>{products.length} products</div>
```

```astro
---
// File: src/pages/bar.astro
import { db } from '@/db'
const products = await db.select().from(productsTable)
---
<div>Products: {products.map(p => p.name).join(', ')}</div>
```

```astro
---
// File: src/pages/ipsum.astro
import { db } from '@/db'
const products = await db.select().from(productsTable)
---
<div>Total: {products.length}</div>
```

Wenn Sie `npm run build` ausführen, führt Astro die Datenbankabfrage **dreimal** aus, einmal pro Seite. Bei großen Datensätzen oder langsamen Datenbanken kann das die Build-Zeit erheblich erhöhen.

## Die Lösung: Ein Cache auf Modulebene

Die eleganteste Lösung ist ein Cache auf Modulebene, der über Seiten-Builds hinweg bestehen bleibt. Da Astros Build-Prozess in einem einzigen Node.js-Prozess läuft, können wir JavaScripts Modul-Caching nutzen, um Abfrageergebnisse zu teilen.

## Schritt-für-Schritt-Implementierung

### Schritt 1: Ein Datenbankverbindungsmodul erstellen

Richten Sie zuerst Ihre Datenbankverbindung ein. In diesem Beispiel verwenden wir Drizzle ORM mit MySQL, aber das Muster funktioniert mit jeder Datenbankbibliothek.

```typescript
// File: src/lib/db/index.ts

import { drizzle } from 'drizzle-orm/mysql2'
import mysql from 'mysql2/promise'

// Create a connection pool
const pool = mysql.createPool({
  host: import.meta.env.DATABASE_HOST,
  user: import.meta.env.DATABASE_USER,
  password: import.meta.env.DATABASE_PASSWORD,
  database: import.meta.env.DATABASE_NAME,
})

// Initialize Drizzle
export const db = drizzle(pool)
```

### Schritt 2: Einen Query-Cache-Helper implementieren

Erstellen Sie nun eine Utility-Funktion, die Abfrageergebnisse auf Modulebene cached:

```typescript
// File: src/lib/db/cache.ts

type CacheEntry<T> = {
  data: T
  timestamp: number
}

// Module-level cache that persists across page builds
const queryCache = new Map<string, CacheEntry<any>>()

/**
 * Execute a database query with caching
 * @param key - Unique identifier for this query
 * @param queryFn - Function that executes the database query
 * @returns Cached or fresh query results
 */
export async function cachedQuery<T>(
  key: string,
  queryFn: () => Promise<T>
): Promise<T> {
  // Check if we have a cached result
  if (queryCache.has(key)) {
    const cached = queryCache.get(key)!
    console.log(`[Cache HIT] Using cached data for: ${key}`)
    return cached.data as T
  }

  // Execute the query if no cache exists
  console.log(`[Cache MISS] Executing query for: ${key}`)
  const data = await queryFn()
  
  // Store in cache
  queryCache.set(key, {
    data,
    timestamp: Date.now(),
  })

  return data
}

/**
 * Clear the entire cache (useful for development)
 */
export function clearCache(): void {
  queryCache.clear()
  console.log('[Cache] Cleared all cached queries')
}

/**
 * Clear a specific cache entry
 */
export function clearCacheKey(key: string): void {
  queryCache.delete(key)
  console.log(`[Cache] Cleared cache for: ${key}`)
}
```

### Schritt 3: Die gecachte Abfrage in Ihren Seiten verwenden

Aktualisieren Sie nun Ihre Seiten, um die gecachte Abfragefunktion zu verwenden:

```astro
---
// File: src/pages/foo.astro
import { db } from '@/lib/db'
import { cachedQuery } from '@/lib/db/cache'
import { productsTable } from '@/lib/db/schema'

const products = await cachedQuery('all-products', async () => {
  return await db.select().from(productsTable)
})
---

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Products - Foo</title>
</head>
<body>
  <h1>All Products</h1>
  <p>{products.length} products available</p>
</body>
</html>
```

```astro
---
// File: src/pages/bar.astro
import { db } from '@/lib/db'
import { cachedQuery } from '@/lib/db/cache'
import { productsTable } from '@/lib/db/schema'

const products = await cachedQuery('all-products', async () => {
  return await db.select().from(productsTable)
})
---

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Products - Bar</title>
</head>
<body>
  <h1>Product List</h1>
  <ul>
    {products.map(product => (
      <li>{product.name}</li>
    ))}
  </ul>
</body>
</html>
```

```astro
---
// File: src/pages/ipsum.astro
import { db } from '@/lib/db'
import { cachedQuery } from '@/lib/db/cache'
import { productsTable } from '@/lib/db/schema'

const products = await cachedQuery('all-products', async () => {
  return await db.select().from(productsTable)
})
---

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Products - Ipsum</title>
</head>
<body>
  <h1>Product Summary</h1>
  <p>Total products: {products.length}</p>
  <p>Average price: ${(products.reduce((sum, p) => sum + p.price, 0) / products.length).toFixed(2)}</p>
</body>
</html>
```

### Schritt 4: Die Optimierung verifizieren

Wenn Sie `npm run build` ausführen, sehen Sie Konsolen-Logs, die zeigen, dass die Abfrage nur einmal ausgeführt wird:

```bash
[Cache MISS] Executing query for: all-products
[Cache HIT] Using cached data for: all-products
[Cache HIT] Using cached data for: all-products
```

Die erste Seite löst die Datenbankabfrage aus; alle folgenden Seiten nutzen das gecachte Ergebnis wieder.

## Fortgeschritten: Cache mit TTL für die Entwicklung

Während der Entwicklung möchten Sie den Cache möglicherweise periodisch aktualisieren. Hier ist eine erweiterte Version mit Time-To-Live (TTL)-Unterstützung:

```typescript
// File: src/lib/db/cache.ts

type CacheEntry<T> = {
  data: T
  timestamp: number
}

const queryCache = new Map<string, CacheEntry<any>>()

// Default TTL: 5 minutes (only used in dev mode)
const DEFAULT_TTL = 5 * 60 * 1000

export async function cachedQuery<T>(
  key: string,
  queryFn: () => Promise<T>,
  options?: {
    ttl?: number
    forceRefresh?: boolean
  }
): Promise<T> {
  const { ttl = DEFAULT_TTL, forceRefresh = false } = options || {}
  
  // Force refresh if requested
  if (forceRefresh) {
    queryCache.delete(key)
  }

  // Check cache
  if (queryCache.has(key)) {
    const cached = queryCache.get(key)!
    const isExpired = import.meta.env.DEV && Date.now() - cached.timestamp > ttl
    
    if (!isExpired) {
      console.log(`[Cache HIT] Using cached data for: ${key}`)
      return cached.data as T
    }
    
    console.log(`[Cache EXPIRED] Refreshing data for: ${key}`)
    queryCache.delete(key)
  }

  // Execute query
  console.log(`[Cache MISS] Executing query for: ${key}`)
  const data = await queryFn()
  
  queryCache.set(key, {
    data,
    timestamp: Date.now(),
  })

  return data
}

/**
 * Get cache statistics
 */
export function getCacheStats() {
  return {
    size: queryCache.size,
    keys: Array.from(queryCache.keys()),
  }
}
```

Sie können es nun mit benutzerdefinierter TTL verwenden:

```astro
---
// Cache for 10 minutes in dev, forever during build
const products = await cachedQuery(
  'all-products', 
  async () => db.select().from(productsTable),
  { ttl: 10 * 60 * 1000 }
)
---
```

## So funktioniert es

Der Cache-Mechanismus funktioniert aufgrund der Art, wie Node.js-Module geladen werden:

1. **Module Singleton**: Die `queryCache`-Map ist auf Modulebene definiert und damit ein Singleton, der für den gesamten Build-Prozess bestehen bleibt.

2. **Build-Prozess**: Während `npm run build` läuft Astro in einem einzigen Node.js-Prozess. Wenn mehrere Seiten dasselbe Modul importieren, liefert Node.js dieselbe Modulinstanz zurück.

3. **Cache Key**: Ein eindeutiger Cache-Key (z. B. `'all-products'`) stellt sicher, dass verschiedene Abfragen nicht kollidieren.

4. **Erste Abfrage**: Die erste Seite, die die Daten benötigt, führt die Abfrage aus und speichert das Ergebnis im Cache.

5. **Folgende Abfragen**: Alle weiteren Seiten prüfen zuerst den Cache und verwenden das vorhandene Ergebnis wieder.

## Performance-Vorteile

Vergleichen wir die Performance-Auswirkungen:

**Ohne Caching:**
- 100 Seiten × 200 ms pro Abfrage = 20 Sekunden Datenbankabfrage-Zeit
- Erhöhte Datenbanklast
- Höhere Cloud-Datenbankkosten

**Mit Caching:**
- 1 Abfrage × 200 ms = 200 ms Datenbankabfrage-Zeit
- Minimale Datenbanklast
- Deutliche Kostensenkung

Ein Praxisbeispiel: Sie haben einen Produktkatalog mit 1.000 Produkten und 50 Seiten, die diese Daten benötigen:

- **Ohne Cache**: 50 Datenbankabfragen während des Builds
- **Mit Cache**: 1 Datenbankabfrage während des Builds
- **Eingesparte Zeit**: ca. 10–30 Sekunden pro Build (abhängig von der Datenbanklatenz)

## Best Practices

1. **Aussagekräftige Cache-Keys verwenden**: Cache-Keys sollten beschreibend und eindeutig sein:

```typescript
// Good
await cachedQuery('products-active', () => db.select().from(products).where(eq(products.active, true)))
await cachedQuery('products-all', () => db.select().from(products))

// Bad
await cachedQuery('data', () => db.select().from(products))
```

2. **Auf der richtigen Ebene cachen**: Cachen Sie Daten, die wirklich seitenübergreifend geteilt werden. Cachen Sie keine seiten-spezifischen Abfragen:

```typescript
// Good: Shared data
await cachedQuery('site-settings', () => db.select().from(settings))

// Bad: Page-specific data
await cachedQuery(`user-${userId}`, () => db.select().from(users).where(eq(users.id, userId)))
```

3. **Cache-Nutzung überwachen**: Logging hinzufügen, um die Cache-Effektivität zu verstehen:

```typescript
import { getCacheStats } from '@/lib/db/cache'

// After build, log cache statistics
console.log('Cache statistics:', getCacheStats())
```

4. **Speicherverbrauch berücksichtigen**: Bei sehr großen Datensätzen Cache-Größenlimits implementieren:

```typescript
const MAX_CACHE_SIZE = 100 // Maximum number of cached queries

if (queryCache.size >= MAX_CACHE_SIZE) {
  // Remove oldest entry
  const oldestKey = queryCache.keys().next().value
  queryCache.delete(oldestKey)
}
```

## Fazit

In dieser Anleitung haben Sie gelernt, wie Sie Datenbankaufrufe während der Static Site Generation in Astro optimieren, indem Sie einen Query-Cache auf Modulebene implementieren. Dieser Ansatz stellt sicher, dass gemeinsame Daten während des Build-Prozesses nur einmal abgerufen werden, das verbessert die Build-Performance deutlich und reduziert die Datenbanklast. Dieses Muster ist besonders wertvoll, wenn Sie content-lastige Sites mit Hunderten oder Tausenden von Seiten bauen, die gemeinsame Datenquellen nutzen.

Bei Fragen oder Anmerkungen erreichen Sie mich gerne auf [Twitter](https://twitter.com/direct_messages/create/rishi_raj_jain_).
