First AI summary
You've built a great review site, but users have to read all reviews to understand a product. AI can summarize hundreds of reviews into a few sentences, saving users time and highlighting key themes.
Outcome
Create a summarizeReviews function that uses Claude via AI Gateway to generate review summaries, and display them on product pages.
Fast Track
- Create
lib/ai-summary.tswithsummarizeReviews(product)usinggenerateText({ model: "anthropic/claude-sonnet-4.5", prompt }) - Create
components/ai-review-summary.tsxas async Server Component that callsawait summarizeReviews(product) - Add
<AIReviewSummary product={product} />toapp/[productId]/page.tsx, test at/mower
Hands-on Exercise 2.2
Implement AI-powered review summarization:
Requirements:
- Create
lib/ai-summary.tswith asummarizeReviewsfunction - Use
generateTextfrom the AI SDK with model"anthropic/claude-sonnet-4.5" - Write a basic prompt that includes all reviews
- Create an
AIReviewSummarycomponent to display the summary - Add the component to product pages
Implementation hints:
generateTextreturns{ text }with the generated content- Use template literals to build the prompt
- The function should be async (AI calls take time)
- Keep the prompt simple for now (we'll improve it in the next lesson)
- The component should be a Server Component (it awaits the async function)
Understanding generateText
The AI SDK provides generateText for one-shot text generation:
import { generateText } from "ai";
const { text } = await generateText({
model: "anthropic/claude-sonnet-4.5",
prompt: "Your instructions here",
});Key parameters:
model: AI Gateway model stringprompt: Your instructions and context
Response:
Returns an object with text (the generated string) plus metadata like token usage.
Step 1: Create AI Summary Function
Create lib/ai-summary.ts:
import { generateText } from "ai";
import { Product } from "./types";
export async function summarizeReviews(product: Product): Promise<string> {
const prompt = `Summarize the following customer reviews for the ${product.name} product:
${product.reviews.map((review) => review.review).join("\n\n")}
Provide a concise summary of the main themes and sentiments in 2-3 sentences.`;
try {
const { text } = await generateText({
model: "anthropic/claude-sonnet-4.5",
prompt,
});
return text;
} catch (error) {
console.error("Failed to generate summary:", error);
throw new Error("Unable to generate review summary. Please try again.");
}
}What this does:
- Takes a Product object
- Builds a prompt with product name and all reviews
- Calls Claude via AI Gateway
- Returns the generated summary
- Handles errors gracefully
Step 2: Create AI Summary Component
Create components/ai-review-summary.tsx:
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Product } from "@/lib/types";
import { summarizeReviews } from "@/lib/ai-summary";
import { FiveStarRating } from "./five-star-rating";
export async function AIReviewSummary({ product }: { product: Product }) {
const summary = await summarizeReviews(product);
const averageRating =
product.reviews.reduce((acc, review) => acc + review.stars, 0) /
product.reviews.length;
return (
<Card className="w-full max-w-prose p-10 grid gap-10">
<CardHeader className="items-center space-y-0 gap-4 p-0">
<div className="grid gap-1 text-center">
<CardTitle className="text-lg">AI Summary</CardTitle>
<p className="text-xs text-muted-foreground">
Based on {product.reviews.length} customer ratings
</p>
</div>
<div className="bg-gray-100 px-3 rounded-full flex items-center py-2 dark:bg-gray-800">
<FiveStarRating rating={Math.round(averageRating)} />
<span className="text-sm ml-4 text-gray-500 dark:text-gray-400">
{averageRating.toFixed(1)} out of 5
</span>
</div>
</CardHeader>
<CardContent className="p-0 grid gap-4">
<p className="text-sm leading-loose text-gray-500 dark:text-gray-400">
{summary}
</p>
</CardContent>
</Card>
);
}Key features:
- Server Component (no
"use client") - Awaits
summarizeReviews()before rendering - Displays average rating with stars
- Shows review count
- Clean card layout
Step 3: Add to Product Page
Update app/[productId]/page.tsx:
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { getProduct, getProducts } from "@/lib/sample-data";
import { Reviews } from "@/components/reviews";
import { AIReviewSummary } from "@/components/ai-review-summary";
export default async function ProductPage({
params,
}: {
params: Promise<{ productId: string }>;
}) {
const { productId } = await params;
let product;
try {
product = await getProduct(productId);
} catch (error) {
notFound();
}
return (
<main className="min-h-screen p-8">
<div className="max-w-4xl mx-auto space-y-8">
<div>
<h1 className="text-4xl font-bold">{product.name}</h1>
<p className="text-lg text-muted-foreground mt-2">
{product.description}
</p>
</div>
<AIReviewSummary product={product} />
<Reviews product={product} />
</div>
</main>
);
}
// ... (generateStaticParams and generateMetadata remain the same)Try It
-
Run your dev server:
pnpm dev -
Visit a product page:
http://localhost:3000/mower -
Watch the terminal:
GET /mower 200 in 2.3sThat 2+ second delay is AI generation time.
-
See the AI summary:
- Summary card appears above reviews
- Claude-generated text summarizes all reviews
- Average rating and review count displayed
-
Try different products:
/ecoBright /aquaHeatEach gets its own unique AI-generated summary.
-
Check AI Gateway dashboard:
- Go to Vercel dashboard → AI Gateway
- See API calls increasing
- Check token usage
- Monitor costs
What you'll notice:
The summaries work but vary in format and quality. Sometimes they include ratings, sometimes word counts, sometimes extra metadata. That's expected with a basic prompt. We'll fix this in the next lesson with prompt engineering.
How It Works
Request Flow:
- User visits
/mower - Next.js renders
ProductPage(Server Component) <AIReviewSummary>component renders- Calls
await summarizeReviews(product) - Function sends prompt to AI Gateway
- AI Gateway forwards to Claude API
- Claude generates summary (~2s)
- Returns text to your app
- Component renders with summary
- Full HTML sent to browser
Server-side rendering: Everything happens on the server. The user's browser never sees the AI Gateway API key or makes direct API calls.
Performance Note
Current behavior:
- Every page load calls Claude
- ~2 second delay per request
- Costs tokens every time
Coming in Section 3:
- Smart Caching (Lesson 3.1)
- Cache summaries for 1 hour
- First request: 2s (generates)
- Subsequent requests: 50ms (cached)
- 97% cost reduction
AI Gateway Dashboard
After visiting a few product pages, check your dashboard:
Metrics you'll see:
- API Calls: 3+ (one per product visited)
- Tokens Used: ~800-1000 per summary
- Cost: ~$0.002 per summary
- Model:
anthropic/claude-sonnet-4.5 - Success Rate: 100%
Example summary generation:
- Input tokens: ~600 (your prompt + reviews)
- Output tokens: ~100 (generated summary)
- Total: ~700 tokens
- Cost: ~$0.0021
Commit
git add lib/ai-summary.ts components/ai-review-summary.tsx app/\[productId\]/page.tsx
git commit -m "feat(ai): add basic AI review summarization"
git pushDone-When
lib/ai-summary.tscreated withsummarizeReviewsfunctiongenerateTextworking with Claude via AI GatewayAIReviewSummarycomponent displays summaries- Summaries appear on all product pages
- AI Gateway dashboard shows API calls and costs
- No errors in terminal or browser console
What's Next
Your AI feature works, but the summaries are inconsistent. In the next lesson, you'll use prompt engineering techniques—few-shot examples, tone guidance, and output constraints—to make summaries production-ready with consistent format and quality.
Sources:
Was this helpful?