Using MongoDB with Nuxt 3 Static Generation
Static site generation with MongoDB requires careful configuration to avoid build issues. When running nuxt generate, the build process needs to connect to your database, fetch data, render pages, and cleanly disconnect.
If you're experiencing issues where Nuxt gets stuck on generate, hangs during the build process, or never completes the static generation, this guide will help you resolve those problems.
Common Problems
Without proper setup, you'll encounter several issues: Nuxt getting stuck on generate and never completing the build process, hanging builds where the Node.js process never exits, timeout errors during database operations like countDocuments(), or pages that render without content. These problems typically occur when database connections aren't properly managed during the static generation process.
Database Connection Setup
Create a reusable database connection utility:
// ~/server/config/database.ts
import mongoose from 'mongoose'
export const connectDB = async () => {
try {
const config = useRuntimeConfig()
const mongoUri = config.mongodbUri || 'mongodb://localhost:27017/yourdb'
if (mongoose.connection.readyState === 1) {
return mongoose.connection
}
const conn = await mongoose.connect(mongoUri)
console.log('MongoDB connected successfully')
return conn
} catch (error) {
console.error('MongoDB connection error:', error)
throw error
}
}
Nitro Plugin Configuration
The critical piece is configuring the Nitro plugin with proper lifecycle management:
// ~/server/plugins/database.ts
import { connectDB } from '~/server/config/database'
import mongoose from 'mongoose'
// Import all models to register them with mongoose
import Author from '~/server/models/Author'
import Post from '~/server/models/Post'
import Project from '~/server/models/Project'
export default nitroPlugin(async (nitroApp) => {
try {
await connectDB()
// Force model registration
const models = [Author, Post, Project]
models.forEach(model => {
void model.modelName
})
console.log('All models registered successfully')
// Cleanup hook - this is essential for static generation
nitroApp.hooks.hook('close', async () => {
if (mongoose.connection.readyState === 1) {
await mongoose.disconnect()
console.log('MongoDB disconnected')
}
})
} catch (error) {
console.error('Database plugin error:', error)
}
})
Nuxt Configuration
Register the plugin in your Nuxt config:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
plugins: ["~/server/plugins/database.ts"],
prerender: {
routes: [
'/',
'/blog',
'/projects'
]
}
},
runtimeConfig: {
mongodbUri: process.env.MONGODB_URI || 'mongodb://localhost:27017/yourdb'
}
})
API Routes for Data Fetching
Create API routes that work during both development and static generation:
// ~/server/api/posts.get.ts
export default defineEventHandler(async (event) => {
try {
const posts = await Post.find().populate('author')
const total = await Post.countDocuments()
return { posts, total }
} catch (error) {
console.error('Error fetching posts:', error)
return { posts: [], total: 0 }
}
})
Page Data Fetching
Use Nuxt's data fetching composables in your pages:
<!-- ~/pages/blog/index.vue -->
<script setup>
const { data: blogData } = await useFetch('/api/posts')
</script>
<template>
<div>
<h1>Blog Posts</h1>
<article v-for="post in blogData.posts" :key="post._id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
</div>
</template>
Key Points
The nitroApp.hooks.hook('close') is essential. Without it, the mongoose connection keeps the Node.js process alive, preventing the build from completing.
Model registration must happen in the plugin to ensure mongoose knows about all your schemas before any API routes are called.
Error handling in API routes prevents build failures if the database is temporarily unavailable.
The database connection happens during build time, allowing real data to be baked into your static HTML files.
Build Process
When you run npm run generate, the following happens:
- Nitro plugin connects to MongoDB
- Models are registered with mongoose
- Pages are pre-rendered, calling API routes as needed
- Database operations fetch real data
- Static HTML files are generated with actual content
- Close hook triggers, disconnecting from MongoDB
- Build completes successfully
This approach gives you the performance benefits of static generation while maintaining dynamic data from your MongoDB database.