Google Gemini AI - Vercel SDK
Copy-paste module for integrating Google Gemini into a Next.js App Router project using the Vercel AI SDK. Covers text generation (sync and streaming), structured object generation, image generation, embeddings, and tool calling — logic only, no UI.
Dependencies
ai: The Vercel AI SDK core —generateText,streamText,generateObject,embed,embedMany,tool, and related types.@ai-sdk/google: The Google Generative AI provider for the AI SDK — turns a Gemini model id into a model instance the SDK can call.zod: Schema validation — both for validating request input and for describing structured generation output.
Folder structure
ai/google/
├── actions.ts # "use server" — public API, call directly from Server/Client Components
├── service.ts # Internal — provider init + raw AI SDK calls
├── client.ts # Internal — fetch helper for the one streaming Route Handler
├── hooks.ts # React hooks for Client Components
├── constants.ts # Internal — models, defaults, limits (single source of truth)
├── types.ts # Shared types — freely importable from anywhere
├── errors.ts # Internal — error normalization (AIResult<T> / AIError)
├── models.ts # Internal — model selection, validation, metadata
├── schemas.ts # Internal — Zod input validation + reusable output schemas
├── rate-limit.ts # Internal — rate limiting (in-memory default, pluggable store)
├── prompts/
│ ├── system.ts # Reusable system prompt presets
│ ├── builder.ts # Prompt composition/templating/trimming helpers
│ ├── summarize.ts # Summarization prompt builder
│ ├── translate.ts # Translation prompt builder
│ └── extract.ts # Field-extraction prompt builder
├── tools/
│ ├── calculator.ts # Example tool: arithmetic evaluation
│ ├── search.ts # Example tool: web search (placeholder — needs a provider)
│ └── index.ts # Central tool registry
├── routes/
│ └── stream-text.route.ts # Copy as-is to app/api/ai/stream-text/route.ts
├── env.example
└── setup.md
File usage
service.ts is internal. It owns provider initialization (reading GOOGLE_GENERATIVE_AI_API_KEY) and every raw AI SDK call — generateText, streamText, generateObject, image generation, embeddings, and tool calling. It doesn't validate input or check rate limits; it assumes whatever it's given is already trustworthy.
actions.ts is the public server-side entry point — a "use server" file. Import from here in Server Components, other Server Actions, or directly from a Client Component's event handler. Each function validates its input with schemas.ts, enforces an internal IP-based rate limit, calls service.ts, and returns a consistent AIResult<T> ({ success: true, data } or { success: false, error }). Because Server Actions can only accept and return serializable data, actions.ts never exposes abortSignal or a rate-limit store as parameters, and strips non-serializable error detail before returning to the client.
client.ts and hooks.ts are the client-side entry points, but their scope is narrower than in a typical module: since actions.ts functions can be called directly from Client Components (no fetch needed), client.ts only exists to consume the one thing a Server Action can't do — a live stream. It talks to the Route Handler generated from routes/stream-text.route.ts. hooks.ts wraps both patterns: useGenerateText, useGenerateObject, and useGenerateImage call actions.ts directly, while useStreamText goes through client.ts.
What actions.ts covers
generateText— single-shot text generation (chat, Q&A, general prompting).generateObject— structured output validated against a Zod schema (form-filling, classification, any typed JSON response).generateImage— image generation via Gemini's multimodal "Nano Banana" models, including reference-image-based editing/consistency.embed/embedMany— text embeddings for semantic search, RAG retrieval, or clustering.generateWithTools— tool-calling / agentic loop over the registry intools/index.ts(extend with your own tools).summarize/translate/extractFields— ready-made task helpers pairing a prompt builder (prompts/) with a schema (schemas.ts).
Streaming text generation is the one capability not in actions.ts — a Server Action can't return a live stream, so it's hooks.ts's useStreamText (client) talking to routes/stream-text.route.ts (the one Route Handler this module ships).
Usage examples
Text generation (server-side)
import { generateText } from "@/ai/google/actions";
const result = await generateText({ prompt: "Explain the AI SDK in one sentence." });
if (result.success) {
console.log(result.data.text);
} else {
console.error(result.error.code, result.error.message);
}
Text generation (Client Component)
"use client";
import { useGenerateText } from "@/ai/google/hooks";
export function AskBox() {
const { generate, data, isLoading, error } = useGenerateText();
return (
<div>
<button onClick={() => generate({ prompt: "Write a haiku about databases." })} disabled={isLoading}>
Generate
</button>
{error && <p>{error.message}</p>}
{data && <p>{data.text}</p>}
</div>
);
}
Streaming (Client Component)
"use client";
import { useStreamText } from "@/ai/google/hooks";
export function ChatBox() {
const { streamedText, isStreaming, start, abort } = useStreamText();
return (
<div>
<button onClick={() => start({ prompt: "Tell me a story." })} disabled={isStreaming}>
Generate
</button>
{isStreaming && <button onClick={abort}>Stop</button>}
<p>{streamedText}</p>
</div>
);
}
Structured output
import { generateObject } from "@/ai/google/actions";
import { z } from "zod";
const result = await generateObject({
prompt: "Extract the recipe name and ingredient count from: 'Grandma's Chili, 9 ingredients'.",
schema: z.object({ name: z.string(), ingredientCount: z.number() }),
});
Image generation
import { generateImage } from "@/ai/google/actions";
const result = await generateImage({
prompt: "A minimalist logo of a fox reading a book, flat vector style.",
aspectRatio: "1:1",
});
if (result.success) {
const { base64, mimeType } = result.data.images[0];
// e.g. `data:${mimeType};base64,${base64}`
}
Embeddings
import { embed, embedMany } from "@/ai/google/actions";
const single = await embed({ value: "The quick brown fox.", taskType: "RETRIEVAL_DOCUMENT" });
const batch = await embedMany({ values: ["doc one", "doc two", "doc three"] });
Tool calling
import { generateWithTools } from "@/ai/google/actions";
const result = await generateWithTools({
prompt: "What's 42 * 17?",
toolNames: ["calculator"], // omit to expose the full tool registry
});
if (result.success) {
console.log(result.data.text);
console.log(result.data.toolCalls);
}
Task helpers: summarize, translate, extractFields
import { summarize, translate, extractFields } from "@/ai/google/actions";
import { z } from "zod";
const s = await summarize({ text: longArticle, length: "short" });
// s.data => { summary: string, keyPoints: string[] }
const t = await translate({ text: "Hello, world!", targetLanguage: "Spanish" });
// t.data => { translatedText, sourceLanguage, targetLanguage }
const e = await extractFields({
text: "Contact: Jane Doe, [email protected]",
fieldDescriptions: { name: "Full name", email: "Email address" },
schema: z.object({ name: z.string(), email: z.string().email() }),
});
// e.data => { name, email }
What you can build with this
This module is a general-purpose AI toolkit, not a single-feature integration — what you build with it depends on which pieces you combine:
- Conversational AI: pair
useStreamTextwith a message-history UI for a chat assistant, orgenerateText/generateWithToolsfor a backend agent. - Content tooling:
summarize,translate, andextractFieldscover article summarizers, localization pipelines, and document-to-structured-data extraction (invoices, resumes, support tickets). - Search and RAG:
embed/embedManyplus your own vector store gives semantic search, recommendation, and retrieval-augmented generation. - Creative/product features:
generateImagecovers on-demand asset generation, product mockups, or avatar/logo generators, including iterative editing via reference images. - Agentic workflows:
generateWithToolsplus thetools/registry is the base for anything that needs the model to call functions — extendtools/index.tswith your own (database lookups, internal APIs, etc.) following thecalculator.ts/search.tspattern.
All of the above share the same validation, error handling (AIResult<T>), and rate-limiting layer, so mixing several of these in one app doesn't mean duplicating that plumbing.