A deep dive into the Eonebill tech stack — Next.js 15, Tailwind CSS, shadcn/ui, Prisma, and AI-powered invoice generation.

Building a SaaS product in the AI space means constantly choosing between shipping fast and building maintainable systems. When we started Eonebill — an AI-powered invoice generator for freelancers and small businesses — we had to make hard decisions about our tech stack. This is the story of what we chose and why.
Here's our core technology stack:
When we started in early 2025, Next.js 14 was dominant and Next.js 15 was in beta. We made a bet on the newer version — and it paid off.
Server Actions changed how we think about form submissions. Invoice creation, client management, payment recording — all handled by server actions with zero API boilerplate. The type safety from next-safe-action means our Zod schemas double as both runtime validators and TypeScript types.
'use server'
import { z } from 'zod'
import { safeAction } from '@/lib/safe-action'
const createInvoiceSchema = z.object({
clientId: z.string().uuid(),
items: z.array(z.object({
description: z.string().min(1),
quantity: z.number().positive(),
rate: z.number().nonnegative(),
})),
dueDate: z.string().datetime(),
notes: z.string().optional(),
})
export const createInvoice = safeAction(createInvoiceSchema, async (parsed) => {
const invoice = await db.insert(invoices).values({
...parsed.data,
userId: await getCurrentUserId(),
}).returning()
return invoice[0]
})
The App Router's nested layouts made our multi-tenant structure natural. Each workspace has its own layout, and since everything is server-rendered by default, our invoices load fast even on slower connections.
We evaluated a few UI approaches. Component libraries like MUI felt heavy and "enterprise-y." CSS-in-JS added runtime overhead we didn't want. Tailwind + shadcn/ui hit the sweet spot.
shadcn/ui isn't a component library you install — it's copy-paste code you own. When we needed a data table for our invoice list, we pulled the Radix-based table component and customized it to match our design system. Zero npm dependency lock-in.
Tailwind's @apply directives let us create semantic utility classes for our design tokens:
@layer components {
.invoice-card {
@apply bg-white border border-gray-200 rounded-xl shadow-sm
hover:shadow-md hover:border-blue-200 transition-all duration-200;
}
.invoice-status-paid {
@apply bg-green-50 text-green-700 border border-green-200;
}
.invoice-status-overdue {
@apply bg-red-50 text-red-700 border border-red-200;
}
}
The result? A design system that feels cohesive but isn't strangling us with abstraction.
We switched from Prisma to Drizzle after hitting migration speed issues in development. Drizzle's schema-as-code approach felt more like writing SQL, which our team appreciated:
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
Migrations are fast, the ORM is lean, and the generated SQL is actually readable. For a product where query performance directly affects invoice generation speed, this matters.
Here's where it gets interesting. The "AI" in AI Invoice Generator isn't a buzzword — it's integrated at multiple layers:
1. Invoice Auto-population
Users describe their project in natural language. GPT-4o parses the description and suggests line items, quantities, rates, and even payment terms based on industry norms.
2. Smart Follow-ups
When an invoice goes overdue, our AI analyzes the client's payment history and the invoice context to suggest a personalized follow-up message. Not a generic "please pay" email — something contextual.
3. Description Enhancement
Freelancers often write vague line item descriptions. Our AI can take "design work" and expand it to "UI/UX design services — 3 logo concepts with 2 revision rounds" based on the broader context of the engagement.
Supporting English and Chinese wasn't just about translation strings. Invoice standards differ between the US and China — tax IDs, currency formatting, legal disclaimers, even the concept of a "receipt" vs. an "invoice" varies. We solved this with next-intl's message files and locale-specific invoice templates:
messages/
en.json
zh.json
Each locale has its own invoice template configuration. The same underlying data renders differently based on the user's locale.
If we started today, we'd probably consider:
But overall, the stack has held up well. We shipped Eonebill to production in under 3 months, and the codebase is still something our team enjoys working in.
The AI invoice generator space is crowded, but most competitors are either template editors with a thin AI wrapper, or enterprise tools that charge $99/month minimum. We built Eonebill for the freelancer who wants AI superpowers without the enterprise complexity.
Try Eonebill free at https://www.eonebill.ai — AI-powered invoice generation for freelancers and small businesses, starting at $0.
Originally published at Eonebill — AI-powered invoice generator for freelancers and small businesses.
Ready to automate your invoicing? Try Eonebill free — no credit card required.
Start Free →Join the community
Subscribe to our newsletter for the latest news and updates