npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-google-reviews

v2.0.0

Published

Svelte component for displaying Google Reviews

Downloads

1,093

Readme

Svelte Google Reviews

CI npm version npm downloads bundle size license

Drop Google Reviews into any Svelte 5 or SvelteKit app. Ships three ready-made layouts (badge, carousel, custom slot), pulls data from either the free Featurable API or the Google Places API, and works out of the box with SSR.

📖 Live docs & interactive examples →

Table of contents

Features

  1. 🛠️ Customizable: Three layout options with extensive CSS class/style prop overrides
  2. 🔎 SEO-friendly: JSON-LD structured data for search engines
  3. 💻 Responsive: Works on all devices and screen sizes
  4. Fast: Lightweight — no React, no Emotion, no jQuery
  5. Free: Use the Featurable API at no cost
  6. 🌱 Fresh: Auto-updates reviews every 48 hours via the Featurable API
  7. ♿️ Accessible: WAI-ARIA compliant carousel and interactive elements
  8. 🪶 Lightweight: Scoped CSS, minimal dependencies (embla-carousel only)

Installation

npm install svelte-google-reviews
yarn add svelte-google-reviews
pnpm add svelte-google-reviews

Usage

Using the Featurable API (recommended)

Prerequisites:

  1. Create a free account at https://featurable.com
  2. Create a new Featurable widget
  3. Click Embed > API and copy the widget ID
<script>
  import { SvelteGoogleReviews } from 'svelte-google-reviews';

  // Create a free widget at https://featurable.com
  const featurableWidgetId = '842ncdd8-0f40-438d-9c...'; // use "example" for testing
</script>

<SvelteGoogleReviews layout="carousel" featurableId={featurableWidgetId} />

Note: The Featurable API is free and provides caching, automatic updates, and unlimited reviews.

Using static reviews (Google Places API or manual)

The Google Places API is limited to the 5 most recent reviews. Fetch them server-side and pass them directly:

<script lang="ts">
  import { SvelteGoogleReviews } from 'svelte-google-reviews';
  import type { GoogleReview } from 'svelte-google-reviews';

  // Fetched server-side via Google Places API
  let { reviews }: { reviews: GoogleReview[] } = $props();
</script>

<SvelteGoogleReviews layout="badge" {reviews} isLoading={false} />

Note: Never expose your Google API key client-side. Always fetch reviews server-side (e.g. in a SvelteKit +page.server.ts load function).

SvelteKit + SSR

The component is SSR-safe: it renders a placeholder on the server and hydrates on the client. The carousel (embla) only initializes in the browser, so there is no window is not defined risk.

Fetching from Google Places API (server-side)

Create a +page.server.ts next to the page that renders <SvelteGoogleReviews />:

// src/routes/+page.server.ts
import { GOOGLE_PLACES_API_KEY, GOOGLE_PLACE_ID } from '$env/static/private';
import type { PageServerLoad } from './$types';
import type { GoogleReview } from 'svelte-google-reviews';

export const load: PageServerLoad = async ({ fetch }) => {
  const url = `https://places.googleapis.com/v1/places/${GOOGLE_PLACE_ID}?fields=reviews,rating,userRatingCount&key=${GOOGLE_PLACES_API_KEY}`;
  const res = await fetch(url);
  const data = await res.json();

  const reviews: GoogleReview[] = (data.reviews ?? []).map((r: any) => ({
    reviewId: r.name ?? null,
    reviewer: {
      displayName: r.authorAttribution?.displayName ?? 'Anonymous',
      profilePhotoUrl: r.authorAttribution?.photoUri ?? '',
      isAnonymous: false,
    },
    starRating: r.rating,
    comment: r.originalText?.text ?? r.text?.text ?? '',
    createTime: r.publishTime ?? null,
    updateTime: r.publishTime ?? null,
  }));

  return { reviews, averageRating: data.rating, totalReviewCount: data.userRatingCount };
};

Then consume it in the page:

<!-- src/routes/+page.svelte -->
<script lang="ts">
  import { SvelteGoogleReviews } from 'svelte-google-reviews';
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
</script>

<SvelteGoogleReviews
  layout="carousel"
  reviews={data.reviews}
  isLoading={false}
  structuredData
  averageRatingOverride={data.averageRating}
  totalReviewCountOverride={data.totalReviewCount}
/>

The $env/static/private import ensures your API key never ships to the client. Cache the load with setHeaders({ 'cache-control': 'public, max-age=3600' }) to avoid hitting the Places quota on every request.

Non-SvelteKit environments

For Vite / Astro / plain Svelte apps, wrap the component in a client-only boundary if your SSR framework doesn't natively support hydration, and fetch reviews at build time or from your own backend.

Layouts

Badge

Displays a compact badge with the average rating, star visualization, and a link to your Google profile.

<SvelteGoogleReviews layout="badge" featurableId={featurableWidgetId} />

Carousel

An interactive, autoplay carousel showing individual review cards.

<SvelteGoogleReviews layout="carousel" featurableId={featurableWidgetId} />

Custom

Provides full control via a children snippet. The reviews array is passed to the snippet.

<SvelteGoogleReviews layout="custom" featurableId={featurableWidgetId}>
  {#snippet children({ reviews })}
    {#each reviews as review}
      <div>
        <strong>{review.reviewer.displayName}</strong>
        <p>{review.comment}</p>
      </div>
    {/each}
  {/snippet}
</SvelteGoogleReviews>

Migration note from React: The React renderer prop is replaced by Svelte 5's children snippet pattern.

Props

Common Props

| Prop | Type | Default | Description | | -------------------------- | ----------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------- | | featurableId | string | — | Featurable widget ID | | reviews | GoogleReview[] | — | Static reviews array (mutually exclusive with featurableId) | | isLoading | boolean | — | Controls loading state when passing reviews manually | | layout | "carousel" \| "badge" \| "custom" | "carousel" | Layout variant | | widgetVersion | "v1" \| "v2" | "v1" | Featurable API version | | apiBaseUrl | string | "https://api.featurable.com" | Custom API base URL | | nameDisplay | "fullNames" \| "firstAndLastInitials" \| "firstNamesOnly" | "firstAndLastInitials" | How reviewer names are shown | | logoVariant | "icon" \| "full" \| "none" | "icon" | Google logo style | | maxCharacters | number | 200 | Max characters before "Read more" truncation | | dateDisplay | "relative" \| "absolute" \| "none" | "relative" | How review dates are shown | | reviewVariant | "card" \| "testimonial" | "card" | Card layout variant | | theme | "light" \| "dark" | "light" | Color scheme | | hideEmptyReviews | boolean | false | Hide reviews without text | | disableTranslation | boolean | false | Use original (untranslated) review text | | accessibility | boolean | true | Enable ARIA roles and focus management | | structuredData | boolean | false | Inject JSON-LD structured data for SEO | | brandName | string | page title | Brand name for structured data | | productName | string | page title | Product name for structured data | | productDescription | string | "" | Product description for structured data | | totalReviewCountOverride | number | — | Required for structured data when passing reviews manually | | averageRatingOverride | number | — | Required for structured data when passing reviews manually | | errorMessage | string | "Failed to load Google reviews..." | Custom error text | | loadingMessage | string | "Loading reviews..." | Custom loading text |

Carousel Props

| Prop | Type | Default | Description | | ------------------ | ------------------------ | -------------------- | ------------------------------------ | | carouselSpeed | number | 3000 | Autoplay interval in ms | | carouselAutoplay | boolean | true | Enable autoplay | | maxItems | number | 3 | Max slides visible at once (desktop) | | readMoreLabel | string | "Read more" | Expand label for truncated reviews | | readLessLabel | string | "Read less" | Collapse label | | showDots | boolean | true | Show navigation dots | | getRelativeDate | (date: Date) => string | built-in | Custom relative date formatter | | getAbsoluteDate | (date: Date) => string | toLocaleDateString | Custom absolute date formatter |

Badge Props

| Prop | Type | Default | Description | | -------------------------- | ----------------------- | ---------------------- | --------------------------- | | profileUrl | string | auto-fetched | Google Business profile URL | | badgeLabel | string | "Google Rating" | Badge heading | | badgeSubheadingFormatter | (n: number) => string | "Read our N reviews" | Custom subheading |

CSS Customization

Every element exposes a *ClassName and *Style prop for targeted overrides. Style props accept inline style strings (e.g. "color: red; font-size: 14px").

<SvelteGoogleReviews
  layout="carousel"
  featurableId="example"
  reviewCardLightClassName="my-card"
  reviewReadMoreLightStyle="color: blue;"
/>

See the live Storybook docs for a full interactive reference of every prop.

GoogleReview Type

type GoogleReview = {
  reviewId: string | null;
  reviewer: {
    profilePhotoUrl: string;
    displayName: string;
    isAnonymous: boolean;
  };
  starRating: number; // 1–5
  comment: string;
  createTime: string | null;
  updateTime: string | null;
};

Storybook

Interactive docs are hosted at ohmybugs.github.io/svelte-google-reviews — every prop, layout, and theme variant has a live example.

Run locally:

npm run storybook

Troubleshooting

Reviews render but styles are missing

Unlike the React version, this package uses Svelte's scoped <style> blocks — no CSS import is required. If styles are missing, check that your bundler processes .svelte files from node_modules (SvelteKit and @sveltejs/vite-plugin-svelte do this by default; custom Vite configs may need include: ['svelte-google-reviews'] in the Svelte plugin options).

window is not defined during SSR

Shouldn't happen — the carousel initializes in onMount. If you see it, you're likely rendering the component before your framework's hydration boundary. In SvelteKit, ensure you're not calling it inside a +page.server.ts file.

Google Places API returns only 5 reviews

That is a hard limit of the Places API. Use the Featurable API (free, includes caching) if you need all reviews.

Reviews look stale

  • Featurable API: refreshes every 48 hours by default.
  • Google Places API: cache your +page.server.ts load with setHeaders({ 'cache-control': 'public, max-age=3600' }) to avoid rate limits — but don't cache longer than a few hours or new reviews won't appear.

Hydration mismatch warning in the console

Make sure the reviews prop is identical between server and client renders. If you compute anything time-based (e.g. relative dates) at the top level, pass a stable value or override getRelativeDate with a deterministic formatter.

TypeScript can't find GoogleReview type

Import types explicitly:

import type { GoogleReview } from 'svelte-google-reviews';

If you see Cannot find module 'svelte-google-reviews' or its corresponding type declarations, ensure your tsconfig.json uses "moduleResolution": "bundler" or "NodeNext".

Differences from react-google-reviews

| Feature | React | Svelte | | --------------------- | --------------------------------- | ----------------------- | | Custom layout | renderer prop (render function) | children snippet | | Style props | React.CSSProperties object | inline style string | | Error/loading content | React.ReactNode | string | | Carousel library | react-slick | embla-carousel-svelte | | CSS | Emotion CSS-in-JS | Scoped <style> blocks | | CSS import required | Yes (dist/index.css) | No |

License

MIT — see LICENSE. By using the Featurable API you agree to the Featurable Terms of Service.

Acknowledgements