You finally have real customer testimonials. You want them on your landing page. So you drop in a widget, paste a script tag, and push to production — then watch your Lighthouse score crater from 94 to 71. This is the most common way founders accidentally punish themselves for doing the right thing.
Embedding testimonials in Next.js doesn't have to cost you page speed. The framework gives you exactly the tools to do it right — but only if you know which ones to reach for. Here's the concrete approach, no hand-waving.
Why Testimonial Widgets Hurt Performance
Most third-party testimonial embeds work by injecting a <script> tag that loads an external JS bundle, fetches data from an API, then renders HTML into a div. That's three sequential round trips before your visitor sees anything. Every one of those trips can block your Largest Contentful Paint.
The usual culprits are render-blocking scripts, layout shifts from late-loading content (killing your CLS score), and large JS bundles that compete with your own code for the main thread. None of this is inevitable. It's just what happens when you treat a testimonial widget like it's a Calendly embed.
Fetch Testimonials at Build Time with getStaticProps
The cleanest solution for most landing pages: pull your testimonials at build time and bake them into the static HTML. No runtime fetch. No layout shift. No external script.
If you're using a tool like aboast to manage your testimonials, you get a simple REST API endpoint. Call it inside getStaticProps, pass the testimonials as props, and render them as plain HTML. Your Wall of Love becomes part of the static page — zero JavaScript required on the client side.
- Call your testimonial API inside getStaticProps (or generateStaticParams in the App Router).
- Render testimonials as a plain React component — just divs, text, and an optional star SVG.
- Set a revalidate value (e.g. 86400 seconds) so new testimonials appear within a day without a full redeploy.
- Add a min-height to your testimonial container so there's no layout shift even on the first paint.
This approach scores a perfect 0 on CLS for the testimonial section and adds zero to your Time to Interactive. It's the right default for any marketing page.
When to Use Dynamic Loading Instead
Static generation works great for a curated set of 5–10 testimonials. But if you want a live, scrollable Wall of Love — maybe 50+ entries that update in real time — you need a different pattern.
Use Next.js dynamic() with ssr: false to lazy-load the testimonial widget only when it enters the viewport. Pair it with an Intersection Observer so it doesn't even start loading until the user scrolls near it. This keeps your initial bundle small and your above-the-fold LCP untouched.
The pattern looks like this: wrap your testimonial component in dynamic(), render a lightweight skeleton placeholder above the fold, then swap in the real component when the ref enters the viewport. Your visitors see content immediately. The testimonials load quietly in the background.
Embedding Testimonials in Next.js with the Script Component
Sometimes you're working with a third-party embed script you can't replace — maybe a video testimonial player. Never use a raw <script> tag in a Next.js app. Use Next.js's built-in <Script> component instead.
- strategy="lazyOnload" — loads after everything else. Good for testimonial carousels that live below the fold.
- strategy="afterInteractive" — loads after the page is interactive. Use this if the widget needs to respond to user events quickly.
- strategy="beforeInteractive" — blocks until loaded. Almost never the right choice for a testimonial widget.
The <Script> component also deduplicates scripts automatically — so if your testimonial widget script is somehow included twice (it happens), Next.js only loads it once. Small thing, real impact.
How the Aboast Workflow Fits Into This
Here's how this actually works end-to-end with aboast. You send a branded collection form to customers right after a key moment — trial-to-paid, a support ticket resolved, a milestone hit. From what we've seen at aboast, founders who send the form within 24 hours of that moment see response rates between 45–60%. Most get their first testimonial within 3–4 days of going live.
Once testimonials are in your dashboard, you have two embed paths. The one-line widget script (use it with the <Script lazyOnload> pattern above). Or the REST API — which is what you want for the getStaticProps approach. You pick the method that matches your performance requirements, not the other way around.
For a typical SaaS landing page, the recommendation is: fetch 5–8 hand-picked testimonials at build time via the API, render them as static HTML, and use the widget script (lazy-loaded) only for a secondary "all testimonials" page where SEO and LCP matter less.
Common Mistakes That Kill Your Core Web Vitals
A few anti-patterns I see constantly. Avoid all of these.
- Putting the embed script in _document.js — it loads on every page, even ones with no testimonials.
- No fixed height on the testimonial container. The page reflows when content loads. CLS spikes. Visitors get annoyed.
- Loading avatar images without next/image. Unoptimized avatars are surprisingly heavy — use the Image component with a fixed width/height.
- Fetching testimonials client-side on every page load with useEffect. This is the worst pattern: slow, no SEO value, and causes layout shift. Use getStaticProps instead.
- Embedding video testimonials with autoplay. Even muted autoplay triggers extra network requests and tanks mobile performance scores.
The Performance Checklist Before You Ship
Before you push your testimonial section to production, run through this quickly.
- Run Lighthouse in an incognito tab. Check LCP, CLS, and TBT before and after adding the testimonial section.
- Check the Network tab. Any third-party requests? Are they deferred?
- Throttle to "Slow 4G" in DevTools. Does the page feel usable before testimonials load? It should.
- Check CLS with the Layout Shift regions overlay. Any red boxes around your testimonial section means you need a fixed container height.
- View page source. If your testimonials appear in the raw HTML, you've nailed the static generation approach.
Also worth reading: how to structure your landing page social proof section for maximum conversion, and when to ask customers for testimonials so you're not embedding an empty widget while you wait.
If you're curious about the collection side of things, video vs text testimonials is worth a read before you decide which format to embed.
The goal isn't just to show testimonials. It's to show them fast enough that they're actually seen — before the visitor bounces.
Embedding testimonials in Next.js the right way is a one-time setup that pays off every day. Static generation for your hero section. Lazy loading for the full wall. The Script component when you need a third-party embed. If you want to skip the plumbing and go straight to having a performant, embeddable testimonial wall — aboast gives you both the API for the static approach and the one-line widget for the lazy-load approach, so you can pick what fits your stack without rebuilding anything.
Try aboast
Collect testimonials in five minutes.
Free forever plan, no credit card required. Get a branded collection link and start shipping social proof today.
Start free