# SaaS Website Template: The 7 Pages Beyond the Home Page

> A SaaS website template needs more than a home page. What pricing, about, careers, changelog, contact, legal and compare pages must hold, built in Next.js.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Nov 5, 2026
- Category: [Build it: screens and apps](https://wingo-ui.com/blog/category/build)
- Reading time: 20 min
- Canonical: https://wingo-ui.com/blog/saas-website-template

## TL;DR

A SaaS website template should cover seven pages besides the home page: pricing, contact, legal, changelog, about, careers and one comparison page per real competitor, each with a single job. In Next.js, put them in one route group whose layout renders the header and footer once, and render each page template with its own header and footer turned off. Before launch, wire every form and plan button to a real handler and remove all sample content.

A SaaS website template usually means a home page and a pricing section with a footer full of links that go nowhere. Then a visitor clicks Careers, Changelog or Privacy and lands on a 404, or on a page that still says Acme. This guide covers the SaaS website pages you need besides the home page: what each one is for, what it must contain to turn a visitor into a sign-up, and how to assemble the whole set in Next.js from page templates, so the site reads as one product instead of seven experiments.

We build Wingo UI, a React component library with [50 page templates](https://wingo-ui.com/templates), so we are not neutral, and the code below uses our templates. The structure works with any React kit or with pages you write by hand. If you only need the home page, the [Landing Page template](https://wingo-ui.com/templates/landing-page) covers it, and our [Next.js SaaS landing page tutorial](https://wingo-ui.com/blog/nextjs-saas-landing-page) builds one section by section. For the top of that page, our [shadcn hero section](https://wingo-ui.com/blog/shadcn-hero-section) post shows six layouts, and an AI product gets its own [AI SaaS landing page template](https://wingo-ui.com/blog/ai-saas-landing-page-template). This post picks up where they end.

## What pages does a SaaS website need besides the home page?

Seven: pricing, contact and legal at launch, then a changelog, an about page, a careers page and one comparison page per real competitor as the company grows. Most templates show you these pages without saying what each one is for.

Search for a startup website template or a Next.js SaaS starter and you get two kinds of results (checked on November 5, 2026). Minimal starters like Vercel's [Minimal Next.js SaaS Website Starter](https://vercel.com/templates/saas/next-js-saas-website-starter) (Next.js 14, Tailwind CSS, next-themes) and NextJSTemplates' free [Startup](https://nextjstemplates.com/templates/startup) give you a home page and a few basic sections. Paid themes like Themefisher's [Copper](https://themefisher.com/products/copper-nextjs) ($77 for a single license, Next.js 16) list 17 pages, including Career, Career Single, Changelog, two pricing layouts, Terms and Privacy. Both kinds show you the pages. Neither tells you what each page has to do.

Here is the set we think a SaaS needs, with the one job each page has:

| Page | Its one job | Must contain | When to add it |
|---|---|---|---|
| Pricing | Answer "what does it cost me?" | Plans, a period switch, a comparison table, billing FAQ | At launch |
| Contact | Catch the buyer who will not self-serve | A form that reaches a person, a reply time, direct channels | At launch |
| Legal | Answer "can I trust you with my data?" | Terms, privacy, cookies, a DPA for B2B, dates | At launch |
| Changelog | Prove the product is alive | Dated releases, grouped changes, RSS | After a few releases |
| About | Put names and a story behind the product | Founders, a timeline, values, a way to reach you | Once there is a story |
| Careers | Hire, and show the company is growing | Open roles with salaries, the process, an apply form | When you hire |
| Compare | Win the buyer who is choosing between you and X | An honest verdict, a feature table, prices with sources | Per real competitor |

Two rules apply to every page. First, one primary action across the whole site: if the home page says "Start for free", the pricing page, the about page and the closing band of a comparison page say the same thing and go to the same route. Second, no page ships with sample content. Every template you start from, ours included, ships with a fictional company, its customers, its numbers and its quotes. A careers page that says "42 people in 11 countries" when you are two founders costs more trust than a missing page.

## How do I set up all the pages as one Next.js site?

Treat the marketing pages as one route group with one layout. The layout renders the header and the footer once, so every page shares the same navigation, the same sign-in link and the same footer columns. Each page then renders its template with `header={false}` and `footer={false}`.

```txt
app/
  (marketing)/
    layout.tsx            header, footer and the router link, once
    providers.tsx         LinkProvider with next/link
    site-nav.tsx          nav items and footer columns
    page.tsx              the home page
    pricing/page.tsx
    about/page.tsx
    careers/page.tsx
    careers/[slug]/page.tsx
    changelog/page.tsx
    changelog/rss.xml/route.ts
    contact/page.tsx
    contact/actions.ts
    legal/[doc]/page.tsx
    compare/[competitor]/page.tsx
  sitemap.ts
```

That tree is the whole Next.js website template: one route group, one layout, one folder per page. Install what it needs with the CLI. Each template pulls in the sections it composes as source files you own:

```bash
npx wingo-ui@latest add marketing-header marketing-footer pricing-page about-page careers-page changelog-page contact-page legal-page compare-page
```

The templates and their links render through `LinkProvider`, so one small client component routes every internal link on every page through `next/link` and tells the header which item is active:

```tsx
// app/(marketing)/providers.tsx
"use client";

import type { ReactNode } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { LinkProvider } from "@/lib/navigation";

export function Providers({ children }: { children: ReactNode }) {
  const router = useRouter();
  return (
    <LinkProvider component={Link} currentPath={usePathname()} navigate={router.push}>
      {children}
    </LinkProvider>
  );
}
```

```tsx
// app/(marketing)/layout.tsx
import type { ReactNode } from "react";
import { MarketingFooter } from "@/components/blocks/marketing-footer";
import { MarketingHeader } from "@/components/blocks/marketing-header";
import { Providers } from "./providers";
import { footerColumns, legalLinks, navItems } from "./site-nav";

export default function MarketingLayout({ children }: { children: ReactNode }) {
  return (
    <Providers>
      <MarketingHeader items={navItems} labels={{ brand: "Acme" }} />
      {children}
      <MarketingFooter
        columns={footerColumns}
        legalLinks={legalLinks}
        socials={[]}
        status={false}
        labels={{ brand: "Acme", tagline: "Plans, docs and automations for small teams." }}
      />
    </Providers>
  );
}
```

Three details in that layout matter. Do not wrap `children` in a `<main>`: each template renders its own `<main id="main">`, which is also where the header's skip link points, and a page with two `main` landmarks confuses screen readers. `socials={[]}` hides the social icons until you have accounts worth linking. And `status={false}` hides the footer's "All systems operational" badge, which links to `/status`, until a real [status page](https://wingo-ui.com/blog/nextjs-status-page) sits behind it.

The [Marketing Header](https://wingo-ui.com/components/marketing-header) shows the links in dropdown panels on desktop and folds them into a bottom sheet below 1024px, and the [Marketing Footer](https://wingo-ui.com/components/marketing-footer) takes your link columns and legal links. Both are free. Keep the navigation short: Product, Pricing, Changelog and Company (About, Careers, Contact) cover most SaaS sites. Legal pages belong in the footer. Our [shadcn navbar guide](https://wingo-ui.com/blog/shadcn-navbar) builds this kind of header in detail, with mega menu panels, a phone menu and a sticky bar.

Last, list every page in `app/sitemap.ts`, including each legal document and each comparison page, so crawlers learn about the dynamic routes without depending on footer links.

## What does a SaaS pricing page need to convert?

The pricing page answers one question, "what does it cost me?", and then removes every reason not to start. In order: plans with a monthly and yearly switch, the featured plan raised, a comparison table for the visitor who is deciding between two plans, a billing FAQ, and a closing call to action. Our [shadcn pricing page tutorial](https://wingo-ui.com/blog/shadcn-pricing-page) goes deep on the comparison table and the yearly math; here is how the whole page fits the site.

The [Pricing Page](https://wingo-ui.com/components/pricing-page) block renders all of it. It is a client component with its own `linkComponent` prop, so it lives in a small client file that also sends the chosen plan and period on to sign-up:

```tsx
// app/(marketing)/pricing/pricing-view.tsx
"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { PricingPage } from "@/components/blocks/pricing-page";
import { comparison, faqs, plans } from "./pricing-data";

export function PricingView() {
  const router = useRouter();
  return (
    <PricingPage
      header={false}
      footer={false}
      linkComponent={Link}
      plans={plans}
      comparison={comparison}
      faqs={faqs}
      currencies={["USD"]}
      showLogos={false}
      showTestimonials={false}
      labels={{ yearlySaving: "2 months free" }}
      onSelectPlan={(planId, period) => router.push(`/sign-up?plan=${planId}&period=${period}`)}
    />
  );
}
```

```ts
// app/(marketing)/pricing/pricing-data.ts
import type { PricingComparisonGroup, PricingPlan } from "@/components/ui/pricing-table";

// yearly is the price of the whole year
export const plans: PricingPlan[] = [
  { id: "free", name: "Free", price: 0, features: ["Up to 3 people", "2 projects"], cta: { label: "Start for free", href: "/sign-up" } },
  { id: "pro", name: "Pro", price: { monthly: 12, yearly: 120 }, priceSuffix: "/user/mo", featured: true, features: ["Unlimited projects", "Automations"], cta: { label: "Start a 14-day trial" }, note: "14 days free, no card" },
  { id: "business", name: "Business", price: { monthly: 24, yearly: 240 }, priceSuffix: "/user/mo", features: ["SSO and audit logs", "Priority support"], cta: { label: "Start a 14-day trial" } },
];

export const comparison: PricingComparisonGroup[] = [
  {
    label: "Workspace",
    rows: [
      { label: "People", values: { free: "Up to 3", pro: "Unlimited", business: "Unlimited" } },
      { label: "Automations", values: { free: false, pro: true, business: true } },
      { label: "SSO", hint: "SAML sign-in with Google Workspace or Microsoft Entra ID.", values: { free: false, pro: false, business: true } },
    ],
  },
];

export const faqs = [
  { question: "Can I switch plans later?", answer: "Yes. Upgrades apply right away; downgrades apply at the next renewal." },
  { question: "What happens when the trial ends?", answer: "You move to the Free plan unless you add a card. Nothing is deleted." },
];
```

`page.tsx` stays a Server Component that exports the metadata and renders `<PricingView />`. Four props in that view decide whether the page tells the truth. A plan whose `cta` has an `href` simply navigates, while a plan without one calls `onSelectPlan` with the period, which is how the Pro button carries "yearly" into sign-up. Without `onSelectPlan` the buttons only show a toast, fine for a demo and useless on a live site. `currencies={["USD"]}` hides the dollar and euro switch: the template converts to euros with a fixed `eurRate`, so leave the switch on only if you really charge in euros. And the "2 months free" badge is a label: if yearly is not ten times monthly in your plans, change it.

> Live demo (Pricing Page): Switch between Monthly and Yearly, then open the comparison below the plans. Narrow the frame below 768px: the plans become a swipeable carousel that starts on the featured plan. Try it at [Pricing Page](https://wingo-ui.com/components/pricing-page) and install it with `npx wingo-ui@latest add pricing-page`.

Leave out the logos and quotes until they are real. A buyer who spots one invented logo starts doubting the prices too, so plans, a table and five honest FAQ answers are the stronger page.

## What goes on a SaaS about page?

The about page is where a buyer checks that real people are behind the product, and where a candidate goes before applying. It needs names and faces, the story in a few dated milestones, what you believe, and a way to reach you or join. The [About Page template](https://wingo-ui.com/templates/about-page) has all of that plus stats, investors, press and offices, and that is the trap: the defaults describe Acme, with 12,000 teams, 42 people and three studios.

```tsx
// app/(marketing)/about/page.tsx
import type { Metadata } from "next";
import { AboutPage } from "@/components/templates/about-page";
import { roles } from "../careers/roles";
import { milestones, team, values } from "./about-content";

export const metadata: Metadata = {
  title: "About Acme",
  description: "Who builds Acme, why we started it and what we believe about small teams.",
};

export default function About() {
  return (
    <AboutPage
      header={false}
      footer={false}
      heroLayout="stacked"
      heroMedia={null}
      milestones={milestones}
      values={values}
      team={team}
      openRoles={roles.length}
      showStats={false}
      showInvestors={false}
      showPress={false}
      showOffices={false}
      labels={{
        heroTitle: "Two engineers who got tired of status meetings",
        heroDescription: "We started Acme in 2025 to keep plans, docs and decisions for small teams in one place.",
      }}
    />
  );
}
```

`heroMedia={null}` hides the drawn collage, whose labels carry Acme's founders and head count; pass a real team photo instead when you have one. `openRoles={roles.length}` reads the same list as the careers page, so the "We're hiring" tile and its count stay true, and at zero the tile disappears. Turn sections back on as they become real: stats when you can publish numbers, press when someone wrote about you.

## How should a careers page handle open roles?

A careers page has two readers: the candidate deciding whether to apply, and the buyer checking whether the company is growing. Both want the same things: roles with a location, a workplace type and a salary range, what the hiring process looks like, and a short apply form that works on a phone. The [Careers Page template](https://wingo-ui.com/templates/careers-page) filters roles by text, department and location, opens each role in a side panel (a bottom sheet on phones) and ends in an apply form with a resume upload.

The form calls `onApply` with the role, the fields and the file. Without it, the template waits a moment and shows its confirmation anyway, so a live careers page can lose every application while looking fine. Send it somewhere real:

```tsx
// app/(marketing)/careers/careers-view.tsx
"use client";

import { CareersPage, type CareersApplication } from "@/components/templates/careers-page";
import { roles } from "./roles";

async function apply(application: CareersApplication) {
  const body = new FormData();
  body.set("role", application.role?.slug ?? "open-application");
  body.set("name", application.name);
  body.set("email", application.email);
  body.set("linkedin", application.linkedin);
  body.set("note", application.note);
  if (application.resume) body.set("resume", application.resume);
  const response = await fetch("/api/apply", { method: "POST", body });
  // a rejected promise shows the form's error message instead of the confirmation
  if (!response.ok) throw new Error("Could not send the application");
}

export function CareersView() {
  return (
    <CareersPage
      header={false}
      footer={false}
      roles={roles}
      onApply={apply}
      showStats={false}
      showTeam={false}
      labels={{
        heroEyebrow: "Careers at Acme",
        heroDescription: "We are two engineers building Acme for small teams, and we are hiring our first designer.",
      }}
    />
  );
}
```

The hero copy matters as much as the handler: the default description says Acme has 42 people across 11 countries, and `stats`, `values`, `perks` and `steps` describe that same fictional company until you pass your own arrays or turn the sections off.

Each role row is also a link to `/careers/<slug>`, and a Cmd or Ctrl click opens it in a new tab, so that route must exist. Build it with the [Job Detail Page template](https://wingo-ui.com/templates/job-detail-page) and add `JobPosting` structured data there. Google's [job posting guidelines](https://developers.google.com/search/docs/appearance/structured-data/job-posting) want the markup on the page of a single job and treat it on a page that lists several jobs as a policy violation, so the detail route is the only place it can go. Our [Next.js JSON-LD guide](https://wingo-ui.com/blog/nextjs-json-ld) shows how to render it.

The [careers page template guide](https://wingo-ui.com/blog/careers-page-template) covers this page in depth: role filters, a job drawer, the apply form and where the `JobPosting` data goes.

## Why does a SaaS need a public changelog?

A changelog proves the product is alive. A buyer comparing two tools opens it to see when the last release shipped, an existing user finds the feature they asked for, and every release becomes a link you can post. It needs dated releases, changes grouped as new, improved and fixed, a permalink per release, and a way to follow it: RSS and an email signup.

The [changelog page template guide](https://wingo-ui.com/blog/changelog-page-template) goes further than this section: a page per release, the RSS feed and the email signup.

The [Changelog Page template](https://wingo-ui.com/templates/changelog-page) takes a `releases` array, newest first. Keep it in a plain data file so the page and the feed read the same list:

```ts
// app/(marketing)/changelog/releases.ts
import type { ChangelogRelease } from "@/components/templates/changelog-page-data";

export const releases: ChangelogRelease[] = [
  {
    version: "1.4.0",
    date: "2026-11-03",
    title: "Recurring tasks",
    summary: "Repeat any task daily, weekly or on a custom schedule.",
    changes: [
      { type: "new", text: "Recurring tasks with daily, weekly and custom schedules.", href: "/docs/recurring-tasks" },
      { type: "improved", text: "Boards with 500 cards open faster." },
      { type: "fixed", text: "Due dates no longer shift by a day across time zones." },
    ],
  },
];
```

The page renders `<ChangelogPage header={false} footer={false} releases={releases} onSubscribe={subscribe} />`, where `subscribe` is a Server Action that takes the email, like the contact action below. One catch: with its own header off, the template no longer reserves room for a sticky bar, so from 768px its version rail sticks 24px from the top and slides under your layout's header. Pass the header height back through `style={{ "--topbar-offset": "65px" } as CSSProperties}` (65px is the default Marketing Header). Its RSS button links to `/changelog/rss.xml` by default (`rssHref`), so build that route instead of shipping a dead link:

```ts
// app/(marketing)/changelog/rss.xml/route.ts
import { releases } from "../releases";

export const dynamic = "force-static";

const SITE = "https://acme.com";
const escape = (text: string) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
// the template's changelogAnchor, copied: its module is client-only. 1.4.0 becomes #v1-4-0
const anchor = (version: string) => `v${version.replace(/[^a-z0-9]+/gi, "-")}`;

export function GET() {
  const items = releases
    .map(
      (release) => `<item>
  <title>${escape(`${release.version}: ${release.title}`)}</title>
  <link>${SITE}/changelog#${anchor(release.version)}</link>
  <guid>${SITE}/changelog#${anchor(release.version)}</guid>
  <pubDate>${new Date(`${release.date}T12:00:00Z`).toUTCString()}</pubDate>
  <description>${escape(release.summary)}</description>
</item>`,
    )
    .join("\n");
  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel>
<title>Acme changelog</title><link>${SITE}/changelog</link><description>New features, improvements and fixes.</description>
${items}
</channel></rss>`;
  return new Response(xml, { headers: { "Content-Type": "application/rss+xml; charset=utf-8" } });
}
```

> Live demo (Changelog Page): Filter by New, Improved or Fixed and watch the count change, then scroll: from 768px the version rail sticks beside the release you are reading. Try it at [Changelog Page](https://wingo-ui.com/templates/changelog-page) and install it with `npx wingo-ui@latest add changelog-page`.

Write entries for users, not for the commit log: "Due dates no longer shift by a day across time zones" tells a customer something; "fix tz offset in date util" does not. The page shows four releases at first (`pageSize`) and loads older ones on demand, so put the release worth announcing first.

## What makes a contact page worth having?

Some buyers will not self-serve: they want a quote, a security questionnaire or a call. The contact page catches them. It needs a short form that reaches a person, a stated reply time you actually keep, the direct channels (sales and support email, a phone number if you answer it), and a link to the privacy policy next to the consent box.

The [Contact Page template](https://wingo-ui.com/templates/contact-page) is free, and it pairs well with a Server Action: an action is the one kind of function a Server Component may pass to a client component, so the page needs no client wrapper.

```ts
// app/(marketing)/contact/actions.ts
"use server";

import type { ContactFormValues } from "@/components/templates/contact-page";

export async function sendContact(values: ContactFormValues) {
  // check again on the server: the browser checks help the user, they do not protect you
  if (!values.consent || !values.name.trim() || !values.email.includes("@") || !values.message.trim()) {
    throw new Error("Invalid message");
  }
  const response = await fetch(process.env.LEADS_WEBHOOK_URL!, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(values),
  });
  if (!response.ok) throw new Error("Could not deliver the message");
}
```

```tsx
// app/(marketing)/contact/page.tsx
import type { Metadata } from "next";
import { ContactPage } from "@/components/templates/contact-page";
import { sendContact } from "./actions";

export const metadata: Metadata = {
  title: "Contact Acme",
  description: "Talk to sales, get support or ask about security. We reply within one business day.",
};

export default function Contact() {
  return (
    <ContactPage
      header={false}
      footer={false}
      onSubmit={sendContact}
      channels={[
        { kind: "email", title: "Sales", value: "sales@acme.com" },
        { kind: "email", title: "Support", value: "support@acme.com" },
      ]}
      showHours={false}
      showOffices={false}
      showLogos={false}
    />
  );
}
```

When the action throws, the form shows its error message and keeps what the visitor typed; when it resolves, the form turns into a "Message sent" panel. As with careers, leaving `onSubmit` out makes every message look sent while going nowhere. Turn the office hours back on only with your real time zone and hours, and replace the sample FAQ with the three questions your inbox actually gets. Our [Next.js contact form tutorial](https://wingo-ui.com/blog/nextjs-contact-form) goes further, with zod validation, a Server Action that sends the email, a honeypot and a rate limit; this is the minimum that works.

## Which legal pages does a SaaS website need?

Terms of service and a privacy policy from day one, a cookie policy if you set anything beyond essential cookies, and a data processing agreement once business customers send you personal data. If you collect personal data from people in the EU, [Article 13 of the GDPR](https://gdpr-info.eu/art-13-gdpr/) lists what the privacy notice must tell them, including who you are, why you process the data, the legal basis, how long you keep it and what rights they have. We are not lawyers, and neither is a template: it gives you the layout, while the words should come from a lawyer or a reviewed generator for your jurisdiction.

The layout still matters, because people do read these pages before they buy. The [Legal Page template](https://wingo-ui.com/templates/legal-page) puts all documents behind one switcher, opens each with its updated and effective dates and a plain-English summary, numbers the sections, links each heading, and turns tables (sub-processors, retention periods, cookies) into cards on phones. Give every document its own URL with a dynamic route:

```tsx
// app/(marketing)/legal/[doc]/page.tsx
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { LegalPage } from "@/components/templates/legal-page";
import { documents } from "./documents";

type Props = { params: Promise<{ doc: string }> };

export const dynamicParams = false;

export function generateStaticParams() {
  return documents.map((document) => ({ doc: document.id }));
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const document = documents.find((item) => item.id === (await params).doc);
  return document ? { title: document.title, description: document.description } : {};
}

export default async function Legal({ params }: Props) {
  const { doc } = await params;
  if (!documents.some((item) => item.id === doc)) notFound();
  return (
    <LegalPage
      header={false}
      footer={false}
      documents={documents}
      doc={doc}
      contact={{ email: "legal@acme.com", company: "Acme Inc.", address: ["100 Main Street", "New York, NY 10001"] }}
    />
  );
}
```

`documents` is a plain data file of `{ id, label, title, description, href, updated, effective, summary, sections }`, where each section body is a list of paragraphs, lists, definitions, tables and notes. Each `href` must match the route (`/legal/privacy` for the `privacy` id), because the switcher and the links inside the text use it. Set `updated` whenever the text changes, and keep the footer's legal links pointing at the same routes. If you set analytics cookies, the [Next.js cookie consent guide](https://wingo-ui.com/blog/nextjs-cookie-consent) covers the banner that goes with the cookie policy.

## When is a comparison page worth building?

When buyers name a competitor in calls, emails or search queries. A comparison page meets someone at the moment of choosing, which makes it one of the most valuable pages on the site and the easiest one to get wrong. The FTC's [1979 statement on comparative advertising](https://www.ftc.gov/legal-library/browse/statement-policy-regarding-comparative-advertising) supports comparisons that are truthful and not deceptive; readers apply a harder test, and leave at the first claim they can disprove.

What the page must contain: a one-line verdict that says who each product suits, a feature table grouped the way buyers think, a price comparison for a real team size with a dated link to the competitor's pricing page, the steps to switch, and an FAQ. The [Compare Page template](https://wingo-ui.com/templates/compare-page) does this per competitor, with a team-size slider that recomputes both yearly costs and says so when the other tool costs less. Its competitor data holds icon components and its labels are functions, so it renders from a client file, and the switcher routes to `/compare/<id>`:

```tsx
// app/(marketing)/compare/[competitor]/compare-view.tsx
"use client";

import { useRouter } from "next/navigation";
import { ComparePage } from "@/components/templates/compare-page";
import { competitors } from "./competitors";

export function CompareView({ id }: { id: string }) {
  const router = useRouter();
  return (
    <ComparePage
      header={false}
      footer={false}
      competitors={competitors}
      competitor={id}
      onCompetitorChange={(next) => router.push(`/compare/${next}`)}
      acmePricing={{ plan: "Pro", perSeat: 12 }}
      showTestimonials={false}
    />
  );
}
```

The server `page.tsx` next to it exports `generateStaticParams` from the same `competitors` list (each `{ competitor: id }`), sets the metadata title to "Acme vs Taskly", and renders `<CompareView id={competitor} />`. Change `labels.brand` and the title labels from Acme to your product name.

> Live demo (Compare Page): Pick another competitor in the switcher, then drag the team size slider: both yearly costs roll to new totals and the band below says which tool costs less. Try it at [Compare Page](https://wingo-ui.com/templates/compare-page) and install it with `npx wingo-ui@latest add compare-page`.

Write one page per competitor and fill the table from their public docs and pricing page, with the date you checked. Say plainly where they are better: a skeptical buyer trusts the rest of the table more when one row goes against you. Our [SaaS comparison page guide](https://wingo-ui.com/blog/saas-comparison-page) walks through the verdict, the dated table and the switch path one part at a time.

## What other pages does a SaaS site add later?

Most sites grow a second ring of pages once the first set is live. Each has its own post:

- A [status page](https://wingo-ui.com/blog/nextjs-status-page), once customers depend on you during working hours.
- A [blog](https://wingo-ui.com/blog/nextjs-blog-template), for the searches your product answers.
- A [404 page](https://wingo-ui.com/blog/react-404-page) that sends lost visitors to search and to the pages above.
- A [waitlist page](https://wingo-ui.com/blog/nextjs-waitlist-page), if you launch before the product is open.
- A [mobile app landing page](https://wingo-ui.com/blog/mobile-app-landing-page-template) with store buttons and a QR code, once you ship a phone app.
- A [help center](https://wingo-ui.com/blog/help-center-template), once the same support questions arrive every week.
- The [sign-in and sign-up pages](https://wingo-ui.com/blog/shadcn-login-page) every pricing button leads to, and the [onboarding flow](https://wingo-ui.com/blog/react-onboarding-flow) after them.

The [templates gallery](https://wingo-ui.com/templates) also has FAQ, security, customers, integrations and roadmap pages. Add them when you have the content, not to make the footer look full.

## Which SaaS website template should you start from?

Pick a free starter if you will write most pages yourself, a paid theme if its design is the one you want, and component templates if the pages will keep changing. A comparison of the options we checked on November 5, 2026:

| Option | Price | What you get | Pick it when |
|---|---|---|---|
| [Vercel Minimal SaaS Starter](https://vercel.com/templates/saas/next-js-saas-website-starter) | Free | A minimal Next.js 14 home page with Tailwind CSS and dark mode | You want a clean start and will write every other page |
| [Startup by NextJSTemplates](https://nextjstemplates.com/templates/startup) | Free (paid Pro tier) | A Next.js 14 startup site with the usual sections | You want a free, ready home page and a pricing section |
| [Copper by Themefisher](https://themefisher.com/products/copper-nextjs) | $77, single license | 17 pages on Next.js 16, including careers, changelog and legal | You want every page as a finished theme in one purchase |
| [Wingo UI templates](https://wingo-ui.com/templates) | Contact and FAQ pages free; the rest in Pro at $8 a month, $80 a year or $150 once | 50 page templates installed as source with the CLI, with props for content and every section toggle | You want each page as a component you configure, sharing one header, footer and set of tokens |

A theme is the faster start when its design is the one you want. Component templates fit better when the site will keep changing: each page takes its content as props, so updating a plan or a release is a data change instead of an edit inside a 600-line file, and the CLI can update the template later while keeping your edits.

## How do I check the whole site before launch?

Click through it as a buyer on a phone, then as a crawler. This list catches most of what goes wrong:

1. Every link in the header and footer resolves: no 404s for careers, changelog, status or legal pages you have not built yet. Remove the link instead.
2. No sample content survived: search the rendered HTML for "Acme", "Emma Carter", "12,000" and the other template defaults.
3. Every form and button reaches a real handler: contact `onSubmit`, careers `onApply`, changelog `onSubscribe`, and pricing `onSelectPlan` or plan links. Submit each one once and check that the message arrives.
4. The primary action reads and routes the same on every page.
5. Every page has its own title and description, and `sitemap.ts` lists the legal documents, comparison pages and job pages.
6. At 390px nothing scrolls sideways, menus and filters open as bottom sheets, and the pricing plans swipe.
7. Light and dark mode are both readable on every page, including the gray bands between sections.

Our guide to [mobile-first React components](https://wingo-ui.com/blog/mobile-first-react-components) explains the phone rules behind items 6 and 7, and the [Build it category](https://wingo-ui.com/blog/category/build) has the other screens, from the [admin dashboard](https://wingo-ui.com/blog/nextjs-admin-dashboard-tutorial) behind the sign-up button to [checkout](https://wingo-ui.com/blog/react-checkout-page). These tutorials build the app behind the website:

- [React settings page](https://wingo-ui.com/blog/react-settings-page): section navigation, a save bar for text fields and a danger zone.
- [API key management UI](https://wingo-ui.com/blog/api-key-management-ui): create, show once, roll and revoke keys, for products with an API.
- [Onboarding checklist UI](https://wingo-ui.com/blog/onboarding-checklist-ui): the Get started card new sign-ups see first.
- [shadcn profile page](https://wingo-ui.com/blog/shadcn-profile-page): the public profile with Follow, tabs and an edit sheet, for products with a community.
- [Role management UI](https://wingo-ui.com/blog/role-management-ui): members and invitations with seats, a permissions matrix and an audit log.
- [React CRM template](https://wingo-ui.com/blog/react-crm-template): a deal pipeline with a weighted forecast and a company record page.
- [React invoice template](https://wingo-ui.com/blog/react-invoice-template): an invoice editor with one totals function for client and server.
- [React marketplace template](https://wingo-ui.com/blog/react-marketplace-template): search, facets and listing cards, with the whole search kept in the URL.
- [shadcn ecommerce template](https://wingo-ui.com/blog/shadcn-ecommerce-template): a storefront from the home page to checkout.
- [Order history page design](https://wingo-ui.com/blog/order-history-page-design): orders by month with Track, Buy again and Invoice, for products that sell things.
- [React appointment booking](https://wingo-ui.com/blog/react-appointment-booking): date, time slot and details, with time zones handled.
- [React workflow builder](https://wingo-ui.com/blog/react-workflow-builder): triggers, branches and AI steps on React Flow.
- [e-Factura VAT calculation](https://wingo-ui.com/blog/e-factura-vat-calculation-javascript) and the [Romanian address format](https://wingo-ui.com/blog/romanian-address-format-react-form): for products that bill Romanian customers.

Not building a SaaS? The same category covers other kinds of sites: a [Next.js portfolio template](https://wingo-ui.com/blog/nextjs-portfolio-template) for a freelancer, a [Next.js agency template](https://wingo-ui.com/blog/nextjs-agency-template) that collects briefs, a [Next.js restaurant template](https://wingo-ui.com/blog/nextjs-restaurant-template) with live hours and table booking, and a [Next.js real estate template](https://wingo-ui.com/blog/nextjs-real-estate-template) with search, a price pin map and a mortgage estimate.

## What is the next step?

Start with the pages a buyer needs on day one: home, pricing, contact and the two legal documents. The [Contact Page template](https://wingo-ui.com/templates/contact-page) is free with a free account, so `npx wingo-ui@latest add contact-page` is a good first install to see how the templates take props. The other templates in this guide are part of [Wingo UI Pro](https://wingo-ui.com/pricing). Whichever SaaS website template you pick, ship fewer pages with real content rather than more pages with sample text.

## Components in this post

- [Landing Page](https://wingo-ui.com/templates/landing-page) (Pro): A SaaS home page: hero with a drawn product shot, logos, bento features, product tour, numbers, quotes, pricing teaser, FAQ and CTA. Install: `npx wingo-ui@latest add landing-page`
- [Pricing Page](https://wingo-ui.com/components/pricing-page) (Pro): A React SaaS pricing page: plans with monthly or yearly and USD or EUR switches, a comparison table, quotes, an FAQ and a closing call. Install: `npx wingo-ui@latest add pricing-page`
- [About Page](https://wingo-ui.com/templates/about-page) (Pro): A React about page template for a SaaS: hero collage, mission, stats, a timeline, values, team, investors, press, offices and careers. Install: `npx wingo-ui@latest add about-page`
- [Careers Page](https://wingo-ui.com/templates/careers-page) (Pro): A React careers page template: mission, team mosaic, values, benefits, filterable open roles with a job drawer and apply form. Install: `npx wingo-ui@latest add careers-page`
- [Changelog Page](https://wingo-ui.com/templates/changelog-page) (Pro): A React changelog page template: email and RSS subscribe, releases on a sticky version rail with drawn covers and New, Improved and Fixed groups. Install: `npx wingo-ui@latest add changelog-page`
- [Contact Page](https://wingo-ui.com/templates/contact-page) (Free): A React contact page template: a validated form, copyable channels with live office hours, offices on a drawn map, a short FAQ and logos. Install: `npx wingo-ui@latest add contact-page`
- [Legal Page](https://wingo-ui.com/templates/legal-page) (Pro): Terms, privacy, cookie and data processing pages in one template: a document switcher, a plain-English summary and numbered sections. Install: `npx wingo-ui@latest add legal-page`
- [Compare Page](https://wingo-ui.com/templates/compare-page) (Pro): A React comparison page template (Acme vs a competitor): a verdict hero, a feature table, price comparison, migration steps and FAQ. Install: `npx wingo-ui@latest add compare-page`
- [Marketing Header](https://wingo-ui.com/components/marketing-header) (Free): A React marketing header for a SaaS site: a sticky navbar with logo, dropdown panels, Sign in, Get started, a news strip and a phone menu. Install: `npx wingo-ui@latest add marketing-header`
- [Marketing Footer](https://wingo-ui.com/components/marketing-footer) (Free): The bottom of a SaaS website: brand and tagline, link columns, a newsletter signup, a status dot, legal links and social icons. Install: `npx wingo-ui@latest add marketing-footer`
- [Job Detail Page](https://wingo-ui.com/templates/job-detail-page) (Pro): A React job posting page template: the role, benefits, interview steps, team, a sticky apply card, an application form and similar roles. Install: `npx wingo-ui@latest add job-detail-page`

## FAQ

### What pages should a SaaS website have?

At launch: a home page, pricing, contact, terms and privacy. Add about and a changelog once there is a story and a release history to show, careers when you hire, and a comparison page for each competitor your buyers actually evaluate against.

### Is there a free Next.js SaaS website template?

Yes. As of November 5, 2026, Vercel lists a free minimal Next.js SaaS website starter and NextJSTemplates offers Startup as a free open source template, both on Next.js 14. They cover the home page and a few basics; paid kits such as Themefisher's Copper ($77) add careers, changelog and legal pages.

### Does a SaaS need a changelog page?

It helps more than its traffic suggests. Buyers check it to see whether the product is maintained, existing users find new features there, and each release gives you a link to share. Ship it with an RSS feed so people can follow it without an account.

### Which legal pages does a SaaS website need?

Terms of service and a privacy policy at minimum, plus a cookie policy if you set non-essential cookies and a data processing agreement if business customers send you personal data. A template gives you the layout; the text should come from a lawyer or a reviewed generator for your jurisdiction.

### Should a startup website have comparison pages?

Only for competitors your buyers actually name in calls, emails or search. Each page should state plainly where the other product is the better choice, use prices you can link to with a date, and offer an import path for people who switch.

---

Source: https://wingo-ui.com/blog/saas-website-template
