Every SaaS template ships a testimonials section. Most fill it with invented people: "Sarah J., Head of Growth at Acme: this changed everything." The template looks finished, the buyer ships it on Friday, and the fake quote is now a claim their company makes to customers.
Since October 2024 that is not just bad taste. The FTC's rule on consumer reviews and testimonials (16 CFR Part 465) makes it unlawful to publish a testimonial from someone who does not exist or did not say it, with civil penalties per violation. The EU's rules on unfair commercial practices say the same about fake reviews. Invented customer logos and made-up results ("3x faster") are the same problem: an endorsement or a factual claim you cannot back up.
The pattern
Keep the section, and make the build decide what may be published.
// src/lib/site.ts
export interface Proof {
sample?: boolean;
}
export interface Testimonial extends Proof {
quote: string;
name: string;
role: string;
company: string;
}
export const SHOW_SAMPLES = process.env.NODE_ENV !== "production";
export function publishable<T extends Proof>(
items: readonly T[],
showSamples: boolean = SHOW_SAMPLES,
): T[] {
return items.filter((item) => showSamples || item.sample !== true);
}
Template content carries sample: true. The section filters its list with
publishable(), renders a small "Sample" badge on every sample it shows, and
returns null when nothing is left:
export function Testimonials({ testimonials }: { testimonials: SiteConfig["testimonials"] }) {
const quotes = publishable(testimonials.items);
if (quotes.length === 0) return null;
// ...each card renders {item.sample ? <SampleBadge /> : null}
}
What this buys you:
- In
next devthe page looks complete, and every invented entry is labelled. next buildinlinesprocess.env.NODE_ENVas"production", so samples are dropped at build time. There is no runtime flag to forget.- A real entry is the same object without the flag. Adding the first real quote brings the section back in production with no code change.
- The same helper covers logos, stats and social profile links.
Test it
Make the production behaviour a unit test, so a later "make the page look fuller" change cannot quietly publish samples:
it("never publishes a sample in production", () => {
for (const list of [site.testimonials.items, site.logos.items]) {
expect(publishable(list, false).some((item) => item.sample)).toBe(false);
}
});
What counts as real
A real testimonial is a customer's own words, quoted accurately, from someone who agreed to be quoted. If you paid for it or gave something in return, say so next to it. A real logo is a customer who agreed to be named. A real number is one you can show the data for. When in doubt, leave the sample flag on and let the section stay hidden.