Skip to content

Commit 8a3aa38

Browse files
committed
fix: Prevents database connection issues during build
Addresses issues where database connections during build or prerendering could cause the build process to hang. This is achieved by: - Adding a utility to detect build time based on environment variables and process arguments. - Excluding the nuxt-users API routes from Nitro's prerendering process, preventing premature database access attempts. - Skipping database connection checks during the build phase to prevent build failures. - Throwing an error if a database connection is attempted during build time to signal misconfiguration. This ensures a more robust build process and prevents unexpected errors due to database unavailability during build time.
1 parent 28662ca commit 8a3aa38

File tree

6 files changed

+164
-6
lines changed

6 files changed

+164
-6
lines changed

src/module.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,18 @@ export default defineNuxtModule<RuntimeModuleOptions>({
240240
nitroConfig.scanDirs = nitroConfig.scanDirs || []
241241
nitroConfig.scanDirs.push(resolver.resolve('./runtime/server/tasks'))
242242

243+
// Automatically exclude nuxt-users API routes from prerendering to prevent build hangs
244+
nitroConfig.prerender = nitroConfig.prerender || {}
245+
nitroConfig.prerender.ignore = nitroConfig.prerender.ignore || []
246+
247+
const apiBasePath = (nuxt.options.runtimeConfig.nuxtUsers as ModuleOptions).apiBasePath || defaultOptions.apiBasePath
248+
const ignorePattern = `${apiBasePath}/**`
249+
250+
if (!nitroConfig.prerender.ignore.includes(ignorePattern)) {
251+
nitroConfig.prerender.ignore.push(ignorePattern)
252+
console.log(`[Nuxt Users] 🔧 Automatically excluding "${ignorePattern}" from prerendering to prevent build hangs`)
253+
}
254+
243255
// NOTE: We no longer configure the database connection here at build time
244256
// because it prevents runtime environment variables from being used.
245257
// The database connection is now established at runtime through useDb()

src/runtime/plugin.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import { defineNuxtPlugin, useRuntimeConfig } from '#app'
22
import type { ModuleOptions } from './utils/imports'
3-
import { checkTableExists } from './server/utils'
3+
import { checkTableExists, isBuildTime } from './server/utils'
44

55
export default defineNuxtPlugin(async (_nuxtApp) => {
66
const { nuxtUsers } = useRuntimeConfig()
7-
// const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig()
87
const options = nuxtUsers as ModuleOptions
9-
// const publicOptions = publicNuxtUsers as ModuleOptions
108

11-
const hasMigrationsTable = await checkTableExists(options, options.tables.migrations)
12-
if (!hasMigrationsTable) {
13-
console.warn('[Nuxt Users] ⚠️ Migrations table does not exist, you should run the migration script to create it by running: npx nuxt-users migrate')
9+
// Skip database checks during build/prerendering to prevent hanging
10+
if (isBuildTime()) {
11+
console.log('[Nuxt Users] 🔧 Build-time detected, skipping database connection checks')
12+
return
13+
}
14+
15+
try {
16+
const hasMigrationsTable = await checkTableExists(options, options.tables.migrations)
17+
if (!hasMigrationsTable) {
18+
console.warn('[Nuxt Users] ⚠️ Migrations table does not exist, you should run the migration script to create it by running: npx nuxt-users migrate')
19+
}
20+
} catch (error) {
21+
// If database connection fails during runtime, warn but don't crash
22+
console.warn('[Nuxt Users] ⚠️ Database connection failed during initialization:', error instanceof Error ? error.message : 'Unknown error')
1423
}
1524
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Detects if we're running during build/prerendering phase
3+
* During prerendering, Nuxt starts a temporary server that should not connect to the database
4+
*/
5+
export const isBuildTime = (): boolean => {
6+
// Check for Nitro prerendering environment variables
7+
if (process.env.NITRO_PRESET === 'nitro-prerender' || process.env.NUXT_ENV === 'prerender') {
8+
return true
9+
}
10+
11+
// Check if we're in a prerendering context
12+
if (process.env.NODE_ENV === 'production' && process.argv.includes('--prerender')) {
13+
return true
14+
}
15+
16+
// Check for common build/prerender indicators
17+
const buildIndicators = ['prerender', 'build', 'generate']
18+
return buildIndicators.some(indicator =>
19+
process.argv.join(' ').toLowerCase().includes(indicator) ||
20+
process.env.npm_lifecycle_event?.includes(indicator)
21+
)
22+
}

src/runtime/server/utils/db.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ export const getConnector = async (name: string) => {
4242
}
4343
}
4444

45+
import { isBuildTime } from './build-time'
46+
4547
export const useDb = async (options: ModuleOptions): Promise<Database> => {
48+
// During build/prerendering, throw an error to prevent hanging
49+
if (isBuildTime()) {
50+
throw new Error('[Nuxt Users] Database connections are not available during build/prerendering phase. This should not happen - please check your prerender configuration.')
51+
}
52+
4653
const cacheKey = JSON.stringify(options.connector)
4754

4855
if (dbCache.has(cacheKey)) {

src/runtime/server/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Internal utils - used by module's API routes, middleware, services, tasks
22
export { useDb, checkTableExists } from './db'
3+
export { isBuildTime } from './build-time'
34
export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime, findUserById, updateUser, deleteUser } from './user'
45
export { createUsersTable } from './create-users-table'
56
export { createPersonalAccessTokensTable } from './create-personal-access-tokens-table'
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2+
import { isBuildTime } from '../../src/runtime/server/utils/build-time'
3+
4+
describe('Build-time Detection', () => {
5+
const originalEnv = process.env
6+
const originalArgv = process.argv
7+
8+
beforeEach(() => {
9+
// Reset environment
10+
process.env = { ...originalEnv }
11+
process.argv = [...originalArgv]
12+
})
13+
14+
afterEach(() => {
15+
// Restore environment
16+
process.env = originalEnv
17+
process.argv = originalArgv
18+
})
19+
20+
describe('Environment Detection', () => {
21+
it('should detect NITRO_PRESET=nitro-prerender', () => {
22+
process.env.NITRO_PRESET = 'nitro-prerender'
23+
expect(isBuildTime()).toBe(true)
24+
})
25+
26+
it('should detect NUXT_ENV=prerender', () => {
27+
process.env.NUXT_ENV = 'prerender'
28+
expect(isBuildTime()).toBe(true)
29+
})
30+
31+
it('should detect build indicators in npm_lifecycle_event', () => {
32+
process.env.npm_lifecycle_event = 'build'
33+
expect(isBuildTime()).toBe(true)
34+
})
35+
36+
it('should detect prerender in process arguments', () => {
37+
process.argv = ['node', 'nuxt', 'build', '--prerender']
38+
expect(isBuildTime()).toBe(true)
39+
})
40+
41+
it('should return false when not in build time', () => {
42+
// Reset to normal runtime environment
43+
delete process.env.NITRO_PRESET
44+
delete process.env.NUXT_ENV
45+
delete process.env.npm_lifecycle_event
46+
process.argv = ['node', 'server.js']
47+
48+
expect(isBuildTime()).toBe(false)
49+
})
50+
})
51+
52+
describe('Database Connection Prevention', () => {
53+
it('should prevent database connections during build time', async () => {
54+
// Set build-time environment
55+
process.env.NITRO_PRESET = 'nitro-prerender'
56+
57+
// Import the db module
58+
const { useDb } = await import('../../src/runtime/server/utils/db')
59+
60+
const mockOptions = {
61+
connector: { name: 'sqlite' as const, options: { path: './test.db' } },
62+
tables: { migrations: 'migrations', users: 'users', personalAccessTokens: 'tokens', passwordResetTokens: 'reset_tokens' },
63+
apiBasePath: '/api/nuxt-users',
64+
passwordResetUrl: '/reset',
65+
emailConfirmationUrl: '/confirm',
66+
auth: { whitelist: [], tokenExpiration: 1440, rememberMeExpiration: 30, permissions: {} },
67+
passwordValidation: {
68+
minLength: 8,
69+
requireUppercase: true,
70+
requireLowercase: true,
71+
requireNumbers: true,
72+
requireSpecialChars: true,
73+
preventCommonPasswords: true
74+
},
75+
hardDelete: false
76+
}
77+
78+
// Should throw an error indicating build-time detection
79+
await expect(useDb(mockOptions))
80+
.rejects
81+
.toThrow('Database connections are not available during build/prerendering phase')
82+
})
83+
})
84+
85+
describe('Production Build Scenarios', () => {
86+
it('should detect production builds with prerender flag', () => {
87+
process.env.NODE_ENV = 'production'
88+
process.argv = ['node', 'nuxt', 'build', '--prerender']
89+
90+
expect(isBuildTime()).toBe(true)
91+
})
92+
93+
it('should detect generate command', () => {
94+
process.argv = ['node', 'nuxt', 'generate']
95+
96+
expect(isBuildTime()).toBe(true)
97+
})
98+
99+
it('should not detect regular production server', () => {
100+
process.env.NODE_ENV = 'production'
101+
process.argv = ['node', 'server.js']
102+
delete process.env.npm_lifecycle_event
103+
104+
expect(isBuildTime()).toBe(false)
105+
})
106+
})
107+
})

0 commit comments

Comments
 (0)