ShipPulse
  • Pricing
  • Docs
  • Blog
  • Compare

Product

TestimonialsChangelogStatus PagesFeedbackRoadmapPricing

Resources

DocsBlogAPI ReferenceSDKHelp Center

Company

AboutContact

Legal

TermsPrivacyCookie PolicyDPASub-processors

Product updates

Changelog updates only. Unsubscribe any time.

ShipPulse operated by Igor Bogdanov, Limassol, Cyprus. [email protected]. Cyprus registration number pending — will be published once issued.

ShipPulse

© 2026 ShipPulse. All rights reserved.

All posts
Next.js
How-to
Developers

How to Add Testimonials to Your Next.js Website

April 18, 2026·7 min read·ShipPulse Team

There are three ways to add testimonials to a Next.js website. Which one you choose depends on how often testimonials change, how much control you need over the UI, and whether you want to manage testimonials from a dashboard or in code.

Option 1: Hardcoded JSX

The fastest approach for a static set of testimonials. You write the testimonials directly into your component:

const TESTIMONIALS = [ { name: "Sarah K.", role: "Founder, Acme SaaS", body: "ShipPulse replaced three tools we were paying for separately.", avatar: "/avatars/sarah.jpg", rating: 5, }, // ... ]; export function TestimonialWall() { return ( <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> {TESTIMONIALS.map((t) => ( <div key={t.name} className="rounded-lg border p-4"> <p className="text-sm mb-3">"{t.body}"</p> <div className="flex items-center gap-2"> <img src={t.avatar} alt={t.name} className="size-8 rounded-full" /> <div> <div className="text-sm font-medium">{t.name}</div> <div className="text-xs text-muted-foreground">{t.role}</div> </div> </div> </div> ))} </div> ); }

When to use it: 3-5 testimonials that rarely change. Good for launch pages where you're hand-picking your strongest social proof.

The problem: Every update requires a code change and a deploy. As your testimonial library grows, this doesn't scale.

Option 2: Headless API with Next.js Server Components

Fetch testimonials at build time (or on each request) from the ShipPulse API. This keeps testimonials in a dashboard while letting you control the rendering in your component:

// app/components/TestimonialWall.tsx (Server Component) import type { Testimonial } from './types'; async function getTestimonials(): Promise<Testimonial[]> { const res = await fetch( 'https://shippulse.dev/api/widget/testimonials?project=proj_xxx&limit=9', { next: { revalidate: 3600 } } // revalidate hourly ); if (!res.ok) return []; const data = await res.json(); return data.testimonials ?? []; } export async function TestimonialWall() { const testimonials = await getTestimonials(); return ( <div className="columns-1 md:columns-3 gap-4"> {testimonials.map((t) => ( <div key={t.id} className="break-inside-avoid mb-4 rounded-lg border p-4"> {'⭐'.repeat(t.rating ?? 5)} <p className="text-sm my-2">"{t.body}"</p> <div className="text-sm font-medium">{t.author_name}</div> <div className="text-xs text-muted-foreground">{t.author_role}</div> </div> ))} </div> ); }

When to use it: You need full control over the UI, but want to manage testimonials from a dashboard. Good when your design system is important and you don't want a third-party widget influencing your page styles.

The problem: You own the rendering code. If you want a new widget type (carousel, popup), you build it yourself.

Option 3: Embeddable widget (recommended for most cases)

Use Next.js's built-in <Script> component with the ShipPulse embed code. The widget renders in an isolated Shadow DOM container — your styles and the widget's styles never conflict:

// app/layout.tsx or a specific page import Script from 'next/script'; export default function LandingPage() { return ( <main> {/* Your page content */} {/* Testimonial wall — renders automatically */} <div id="testimonials-section" /> <Script src="https://cdn.shippulse.dev/widget.js" data-project="proj_your_id" data-widget="testimonials-wall" data-target="#testimonials-section" strategy="lazyOnload" /> </main> ); }

For a carousel in your hero section:

<Script src="https://cdn.shippulse.dev/widget.js" data-project="proj_your_id" data-widget="testimonials-carousel" data-theme="auto" data-autoplay="true" data-interval="4000" strategy="lazyOnload" />

When to use it: Most situations. You get instant access to 9 widget types, automatic updates when you approve new testimonials, zero maintenance, and no CSS conflicts.

The trade-off: Less control over the exact markup. The widget renders its own HTML inside Shadow DOM — you can configure it via data-* attributes but can't rewrite the template.

Which approach should you use?

For most Next.js landing pages: Option 3. The embed widget gives you the most widget types (wall, carousel, popup, badge, ticker, single card, avatar row, slider, grid) with zero maintenance burden.

If your design system requires pixel-perfect control and you're willing to maintain the rendering code: Option 2. The API is simple and the data model is clean.

Option 1 is only appropriate for a controlled launch page with 3-5 hand-picked quotes that won't change. For anything dynamic, it's technical debt from day one.

Add testimonials to your Next.js site in 5 minutes

Create a free ShipPulse account, collect your first testimonials, and embed them with one Script tag. No credit card required.

Start free

Continue reading

Testimonial Widget for Website: Add Social Proof in 2 Minutes →Why Social Proof is the Highest-ROI Marketing Channel for SaaS in 2026 →