Google Reviews
stableFetches recent Google Business reviews through the Maps Data API on RapidAPI, normalizes the response, filters eligible reviews, and prepares them for use in Astro components.
The feature includes a full reviews section and an optional ticker for shorter review excerpts.
Demo
Review ticker
UluBit gave me a much cleaner starting point…

UluBit recent customer reviews
A preview generated from the bundled review data.

Maya Chen

Daniel Foster

Priya Raman

Marcus Reed

Sofia Alvarez

Noah Williams
The demo uses normalized local data rather than making a live API request. It demonstrates how consumers render an already prepared ReviewsData object.
Included components
Reviews.astrorenders the business summary and review cards.ReviewTicker.astrorotates through shortened review excerpts.getReviews()fetches and prepares live or fixture data.getReviewSnippets()converts full reviews into ticker-ready excerpts.refresh-reviews-fixture.mjsgenerates the local fixture.
Setup
Copy the complete feature folder into the project:
src/features/google-reviews/Add the required environment values:
GOOGLE_BUSINESS_ID=
RAPIDAPI_KEY=
GOOGLE_REVIEWS_LIVE=falseAdd the fixture command to package.json:
{
"scripts": {
"reviews:refresh": "node --env-file=.env src/features/google-reviews/scripts/refresh-reviews-fixture.mjs"
}
}Generate the initial fixture:
pnpm reviews:refreshThe feature is provided with an empty fixture. Generate it after configuring a project so it contains reviews for the correct business.
Usage
Call getReviews() once from the page or shared parent that renders the review UI:
---
import {
getReviews,
getReviewSnippets,
} from "@/features/google-reviews";
import Reviews from "@/features/google-reviews/components/Reviews.astro";
import ReviewTicker from "@/features/google-reviews/components/ReviewTicker.astro";
const googleBusiness = await getReviews();
const reviewSnippets = getReviewSnippets(
googleBusiness?.reviews ?? [],
{
maxWords: 8,
maxReviews: 8,
},
);
---
{googleBusiness && (
<>
<section class="reviews-section">
<div class="container">
<Reviews
{googleBusiness}
heading="Recent customer reviews"
subheading="What customers are saying about us."
maxReviews={6}
/>
</div>
</section>
<ReviewTicker
reviews={reviewSnippets}
label="Customer review excerpts"
/>
</>
)}Reviews does not provide its own page section, container, background, or outer spacing. Those layout concerns belong to the consuming page.
ReviewTicker is independent from the main reviews section and can instead be rendered in a hero, social-proof strip, or another suitable location.
Data flow
getReviews()
↓
normalize and filter reviews
↓
ReviewsData
├── Reviews
└── getReviewSnippets()
↓
ReviewTicker
Both components should receive data derived from the same getReviews() result. Components do not fetch reviews independently.
Review selection
By default, the feature requests the 20 newest reviews and retains reviews that:
- Have a five-star rating.
- Contain more than 100 characters.
- Include a valid author, avatar, review text, rating, and timestamp.
These values can be changed in:
src/features/google-reviews/server/reviews.tsThe maxReviews option belongs to each consumer:
Reviews component: first 6 eligible reviews
Review ticker: first 8 eligible reviews
Development and production behavior
During development, the generated fixture is used by default.
To test the live request locally set:
GOOGLE_REVIEWS_LIVE=trueIn production, the feature requests live reviews. If a configured API request fails or returns invalid data, it uses the generated fixture.
Missing RAPIDAPI_KEY or GOOGLE_BUSINESS_ID values cause an explicit error rather than silently using the fixture.
Project dependencies
The component styles currently use project-level:
- CSS custom properties.
- Global typography and layout classes.
- The SCSS breakpoint mixin from
src/styles/breakpoints.
Adapt these styles and imports when copying the feature into a project with different design foundations.