.claude/skills/dynamic-imports/SKILL.md
Why and how to use dynamic imports in LivestockAI server functions for Cloudflare Workers compatibility
npx skillsauth add captjay98/gemini-livestockai Dynamic ImportsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
LivestockAI requires dynamic imports in server functions for Cloudflare Workers compatibility. This skill explains the pattern and why it's necessary.
Cloudflare Workers doesn't support process.env at module load time. Environment variables are only available through the env binding from cloudflare:workers, and that binding is only accessible during request handling.
// ❌ This fails on Cloudflare Workers
// The import runs at module load time, before env is available
import { db } from '~/lib/db'
export const fn = createServerFn().handler(async () => {
return db.selectFrom('users').execute() // db is undefined or throws
})
Use dynamic imports inside the handler function:
// ✅ This works on Cloudflare Workers
export const fn = createServerFn().handler(async () => {
const { getDb } = await import('~/lib/db')
const db = await getDb()
return db.selectFrom('users').execute()
})
getDb() FunctionThe getDb() function in app/lib/db/index.ts handles environment detection:
async function getDatabaseUrl(): Promise<string | undefined> {
// Try process.env first (Node.js, Bun, Vite dev server)
if (typeof process !== 'undefined' && process.env?.DATABASE_URL) {
return process.env.DATABASE_URL
}
// Fall back to Cloudflare Workers env binding
try {
const { env } = await import('cloudflare:workers')
return env.DATABASE_URL
} catch {
return undefined
}
}
export async function getDb(): Promise<Kysely<Database>> {
if (!dbInstance) {
const databaseUrl = await getDatabaseUrl()
if (!databaseUrl) {
throw new Error('DATABASE_URL not set')
}
dbInstance = new Kysely<Database>({
dialect: new NeonDialect({ neon: neon(databaseUrl) }),
})
}
return dbInstance
}
export const createBatchFn = createServerFn({ method: 'POST' })
.inputValidator(schema)
.handler(async ({ data }) => {
// Dynamic import for auth
const { requireAuth } = await import('../auth/server-middleware')
const session = await requireAuth()
// Dynamic import for database
const { getDb } = await import('~/lib/db')
const db = await getDb()
// Dynamic import for utilities that need env
const { checkFarmAccess } = await import('../auth/utils')
await checkFarmAccess(session.user.id, data.farmId)
return insertBatch(db, data)
})
For seeders, migrations, and CLI scripts running in Node.js/Bun, use static imports:
// seeders/production.ts - runs in Node.js/Bun
import { db } from '~/lib/db'
await db.insertInto('users').values({...}).execute()
const { requireAuth } = await import('../auth/server-middleware')
const session = await requireAuth()
const { checkFarmAccess, getUserFarms } = await import('../auth/utils')
const hasAccess = await checkFarmAccess(userId, farmId)
const { getDb } = await import('~/lib/db')
const db = await getDb()
// ❌ Static import of db
import { db } from '~/lib/db'
// ❌ Old pattern - doesn't work
const { db } = await import('~/lib/db')
// ❌ Accessing process.env directly
const url = process.env.DATABASE_URL
Dynamic imports are cached by the JavaScript runtime. The first import loads the module, subsequent imports return the cached module. The getDb() function also maintains a singleton database instance.
neon-database - Database connection detailscloudflare-workers - Deployment environmenttanstack-start - Server function patternsdata-ai
Input validation patterns with Zod in LivestockAI server functions
testing
Unit testing patterns with Vitest in LivestockAI
tools
Server → Service → Repository pattern for feature organization
data-ai
Server-side rendering and server functions with TanStack Start in LivestockAI