Most developers wanting to build an AI SaaS fall into the same trap: they buy a bloated \$199-\$299 boilerplate that is essentially a heavy wrapper around legacy OpenAI APIs.
The result?
- High latency: 800ms+ Time-To-First-Token (TTFT).
- Runaway token costs: You pay from day 1 before validating product-market fit.
- Vendor lock-in: Hardcoded API endpoints that break as LLM models evolve.
Today, we're building a production-grade, zero-operating-cost AI SaaS architecture using Next.js 14 App Router, Google's official @google/genai SDK, Gemini 2.5 Flash, and Stripe Checkout.
⚡ Why Gemini 2.5 Flash for Micro-SaaS?
- Sub-200ms TTFT: Streaming responses feel instantaneous in the UI.
- 1 Million Token Context Window: Handle massive customer documents, chat history, or codebases without expensive chunking databases.
- 100% Free-Tier Architecture: Google AI Studio provides high-rate-limit free tiers, meaning you can test, launch, and validate with \$0/month operating cost.
- Native Multimodal Vision: Extract invoices, receipts, and images natively without fragile OCR/Tesseract libraries.
🏗️ Architecture Overview
Our stack is completely decoupled and modular:
- Frontend & API: Next.js 14 (App Router, Server Actions, Tailwind CSS).
-
AI Engine: Google Gen AI SDK (
@google/genai) with dynamic model selection viaGEMINI_MODELenvironment variable. -
Billing: Stripe Checkout Session flow with cryptographic webhook signature verification (
constructEvent). - Hosting: Vercel / GitHub Pages ($0/mo tier).
🛠️ Step 1: Initialize the Modern Gemini SDK
Install the official Google Gen AI SDK:
npm install @google/genai
Configure your decoupled Gemini client in lib/gemini.ts. Notice the dynamic model fallback to ensure your app automatically scales to Gemini 3.x without code rewrites:
// lib/gemini.ts
import { GoogleGenAI } from "@google/genai";
const apiKey = process.env.GEMINI_API_KEY || "";
export const ai = new GoogleGenAI({ apiKey });
export async function generateAgentResponse(
prompt: string,
systemInstruction?: string,
modelName: string = process.env.GEMINI_MODEL || "gemini-2.5-flash"
) {
if (!apiKey) {
throw new Error("GEMINI_API_KEY is not set.");
}
const response = await ai.models.generateContent({
model: modelName,
contents: prompt,
config: {
systemInstruction: systemInstruction || "You are an intelligent business assistant.",
temperature: 0.7,
},
});
return response.text;
}
export async function streamAgentResponse(
prompt: string,
systemInstruction?: string,
modelName: string = process.env.GEMINI_MODEL || "gemini-2.5-flash"
) {
if (!apiKey) {
throw new Error("GEMINI_API_KEY is not set.");
}
return await ai.models.generateContentStream({
model: modelName,
contents: prompt,
config: {
systemInstruction: systemInstruction || "You are an intelligent business assistant.",
temperature: 0.7,
},
});
}
🚀 Step 2: Next.js 14 Edge-Ready API Route
Create an API route in app/api/agent/route.ts that handles client requests with error handling:
// app/api/agent/route.ts
import { NextResponse } from "next/server";
import { generateAgentResponse } from "@/lib/gemini";
export async function POST(req: Request) {
try {
const { prompt, systemPrompt } = await req.json();
if (!prompt) {
return NextResponse.json({ error: "Missing prompt" }, { status: 400 });
}
const reply = await generateAgentResponse(
prompt,
systemPrompt || "You are an automated SaaS copilot. Answer concisely."
);
return NextResponse.json({ result: reply });
} catch (error: any) {
console.error("Gemini Agent API Error:", error);
return NextResponse.json(
{ error: error.message || "Failed to generate AI response" },
{ status: 500 }
);
}
}
💳 Step 3: Production Stripe Checkout & Webhook Security
Never trust client-side confirmations for billing! Here is how to create a Checkout Session and cryptographically verify Stripe webhooks using the raw request body:
Checkout API (app/api/checkout/route.ts)
import { NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
apiVersion: "2023-10-16" as any,
});
export async function POST(req: Request) {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: "Pro AI Tier Subscription",
description: "Unlimited AI Generations + B2B Document Extraction",
},
unit_amount: 2900, // $29.00
},
quantity: 1,
},
],
mode: "payment",
success_url: `${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"}/dashboard`,
});
return NextResponse.json({ url: session.url });
} catch (err: any) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
}
Cryptographic Webhook Handler (app/api/webhook/route.ts)
import { NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
apiVersion: "2023-10-16" as any,
});
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get("stripe-signature") || "";
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET || ""
);
} catch (err: any) {
console.error("Webhook signature verification failed:", err.message);
return new NextResponse(`Webhook Error: ${err.message}`, { status: 400 });
}
if (event.type === "checkout.session.completed") {
const session = event.data.object as Stripe.Checkout.Session;
console.log("Payment successful for customer:", session.customer_details?.email);
// Provision license or update database
}
return NextResponse.json({ received: true });
}
📄 Bonus: B2B Multimodal Document Parsing in 10 Lines
Need to extract vendor invoices, tax IDs, and totals from PDF/image uploads? With Gemini 2.5 Vision, you don't need Tesseract or heavy OCR servers.
Here is the Python FastAPI service using strict Pydantic V2 schemas:
from google import genai
from pydantic import BaseModel, Field
class InvoiceExtraction(BaseModel):
vendor_name: str = Field(description="Name of the issuer or company")
vat_number: str = Field(description="Tax ID / VAT number / Partita IVA")
total_amount: float = Field(description="Final invoice balance")
currency: str = Field(description="Currency symbol or code (USD, EUR)")
client = genai.Client()
def extract_invoice(image_bytes: bytes, mime_type: str = "image/jpeg") -> InvoiceExtraction:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[
genai.types.Part.from_bytes(data=image_bytes, mime_type=mime_type),
"Extract structured invoice metadata strictly adhering to the schema."
],
config=genai.types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=InvoiceExtraction,
)
)
return InvoiceExtraction.model_validate_json(response.text)
🎮 Try the Interactive Playground & Source Code
We built and open-sourced an entire suite covering these 3 decoupled modules:
- Next.js 14 SaaS Starter with dark-mode UI and SSE streaming.
- B2B Invoice & Document Parser with zero OCR dependencies.
- Customer Support Triage Bot with sentiment analysis and auto-drafting.
- 🌐 Live Interactive In-Browser Demo: https://skappax.github.io/gemini-ai-suite/
- 💻 Open-Source GitHub Repository: https://github.com/skappax/gemini-ai-suite
Feel free to star the repo or clone the starter template for your next AI venture!
Top comments (1)
One thing worth adding before anyone ships this: /api/agent has no auth and no per-user limit, and it takes systemPrompt from the client. Anyone who finds the route can use your Gemini key as a free general-purpose endpoint, and on the free tier they'll use up your rate limit before your real users do.
I'd check the session inside the route, drop the client systemPrompt (or pick one from a fixed list on the server), and count generations per user id in the database. If you give free generations on signup, tie the allowance to a normalised email (lowercase, strip +tags, and remove dots for Gmail) with a unique index, otherwise the same person just signs up again.