Rating counts and averages that update on a nightly batch job rather than in real time allow the displayed schema-org aggregateRating to diverge from the database truth for hours after a review is submitted. A listing that receives 10 new 1-star reviews after a service failure continues displaying its pre-incident 4.5-star average until the batch runs — actively misleading users making decisions based on the structured data. Data-integrity is the core taxon: the JSON-LD reviewCount fed to Google must match the actual review count at page-render time. Delayed updates also suppress Google's trust in your rating freshness, reducing the likelihood of rich-result display.
High because batch-delayed rating updates allow structured data to misrepresent actual listing quality for hours, creating a data-integrity gap that Google's structured data guidelines prohibit.
Recalculate averageRating and reviewCount synchronously in the same transaction that writes the review. Avoid recomputing from a full table scan on every submit — maintain running aggregates on the listing row.
// src/app/api/listings/[id]/reviews/route.ts
export async function POST(request: Request, { params }: { params: { id: string } }) {
const { rating, text } = await request.json();
await db.$transaction(async (tx) => {
await tx.review.create({ data: { listingId: params.id, rating, text } });
// Recompute aggregate from all reviews in the same transaction
const { _avg, _count } = await tx.review.aggregate({
where: { listingId: params.id },
_avg: { rating: true },
_count: { rating: true },
});
await tx.listing.update({
where: { id: params.id },
data: {
averageRating: parseFloat((_avg.rating ?? 0).toFixed(2)),
reviewCount: _count.rating,
dateModified: new Date(),
},
});
});
return Response.json({ ok: true });
}
The dateModified update in the same transaction ensures the JSON-LD timestamp stays current.
ID: directory-listing-schema.data-freshness.rating-freshness
Severity: high
What to look for: Count all listings with ratings. For each, find the review submission API or logic. After a new review is submitted, check whether the listing's averageRating and reviewCount are immediately recalculated and persisted. Look for cache invalidation or real-time updates.
Pass criteria: Review counts and ratings are recalculated within seconds of a new review being submitted (or approved) — ratings older than 6 months must be flagged or weighted differently in the aggregate score. Report even on pass: "X rated listings checked, Y have ratings older than 6 months."
Fail criteria: Ratings are updated only on a schedule (e.g., hourly) or not updated until the next page refresh.
Skip (N/A) when: Listings don't have reviews.
Detail on fail: Example: "Review submitted but reviewCount stays same for 1 hour until batch job runs" or "New review changes rating but listing page doesn't reflect it until next visit"
Remediation: Recalculate ratings immediately on review submission:
export async function POST(request) {
const { listingId, rating, text } = await request.json()
const review = await db.review.create({
data: { listingId, rating, text }
})
const reviews = await db.review.findMany({
where: { listingId }
})
const avgRating = reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length
const listing = await db.listing.update({
where: { id: listingId },
data: {
averageRating: parseFloat(avgRating.toFixed(2)),
reviewCount: reviews.length,
dateModified: new Date()
}
})
return NextResponse.json(listing)
}