Customize Marketing Pages in the Tanstack Start Supabase Turbo Starter Kit

Build and customize landing pages, pricing pages, FAQ, and other marketing content using Tanstack Start App Router and Tailwind CSS.

Marketing pages in Makerkit live at apps/web/src/routes/_marketing/ and include landing pages, pricing, FAQ, blog, changelog, and contact forms. These pages use TanStack Router file-based routing for fast initial loads and SEO optimization.

Marketing Pages Development

Customize and extend your marketing pages

Marketing Pages Structure

The marketing pages follow TanStack Router file-based conventions. The _marketing prefix is a pathless layout route — it wraps its children with a shared header/footer layout without adding a URL segment:

apps/web/src/routes/_marketing/
├── route.tsx # Shared layout with header/footer
├── index.tsx # Home page (/)
├── cookie-policy.tsx # Legal pages
├── privacy-policy.tsx
├── terms-of-service.tsx
├── contact.tsx # Contact form
├── faq.tsx # FAQ page
├── pricing.tsx # Pricing page
├── blog/ # Blog listing and posts
│ ├── index.tsx
│ └── $slug.tsx
└── changelog/ # Product changelog
├── index.tsx
└── $slug.tsx

Shared marketing components (header, footer, navigation) live in apps/web/src/components/marketing/.

Pathless Layout Routes Explained

The _marketing folder is a pathless layout route: the leading underscore means it shares route.tsx's layout without contributing a URL segment. Pages inside render at the root level:

File PathURL
_marketing/index.tsx/
_marketing/pricing.tsx/pricing
_marketing/blog/index.tsx/blog

Customizing Existing Pages

Home Page

The home page at apps/web/src/routes/_marketing/index.tsx typically includes:

apps/web/src/routes/_marketing/index.tsx

import { createFileRoute } from '@tanstack/react-router';
import { Hero } from '#/components/marketing/hero.tsx';
import { Features } from '#/components/marketing/features.tsx';
import { Testimonials } from '#/components/marketing/testimonials.tsx';
import { Pricing } from '#/components/marketing/pricing-section.tsx';
import { CallToAction } from '#/components/marketing/call-to-action.tsx';
export const Route = createFileRoute('/_marketing/')({
component: Home,
});
function Home() {
return (
<>
<Hero />
<Features />
<Testimonials />
<Pricing />
<CallToAction />
</>
);
}

Each section is a separate component in components/marketing/ for easy customization.

Pricing Page

The pricing page displays your billing plans. It reads configuration from apps/web/src/config/billing.config.ts:

apps/web/src/routes/_marketing/pricing.tsx

import { createFileRoute } from '@tanstack/react-router';
import { PricingTable } from '@kit/billing-gateway/marketing';
import billingConfig from '#/config/billing.config.ts';
import pathsConfig from '#/config/paths.config.ts';
const paths = {
signUp: pathsConfig.auth.signUp,
return: pathsConfig.app.home,
};
export const Route = createFileRoute('/_marketing/pricing')({
component: PricingPage,
});
function PricingPage() {
return (
<div className="container mx-auto py-16">
<h1 className="text-4xl font-bold text-center mb-4">
Simple, Transparent Pricing
</h1>
<p className="text-muted-foreground text-center mb-12">
Choose the plan that fits your needs
</p>
<PricingTable config={billingConfig} paths={paths} />
</div>
);
}

See Billing Configuration for customizing plans and pricing.

FAQ Page

The FAQ page uses an accordion component with content from a configuration file or CMS:

apps/web/src/routes/_marketing/faq.tsx

import { createFileRoute } from '@tanstack/react-router';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@kit/ui/accordion';
const faqs = [
{
question: 'How do I get started?',
answer: 'Sign up for a free account and follow our getting started guide.',
},
{
question: 'Can I cancel anytime?',
answer: 'Yes, you can cancel your subscription at any time with no penalties.',
},
// ... more FAQs
];
export const Route = createFileRoute('/_marketing/faq')({
component: FAQPage,
});
function FAQPage() {
return (
<div className="container mx-auto max-w-3xl py-16">
<h1 className="text-4xl font-bold text-center mb-12">
Frequently Asked Questions
</h1>
<Accordion type="single" collapsible>
{faqs.map((faq, index) => (
<AccordionItem key={index} value={`item-${index}`}>
<AccordionTrigger>{faq.question}</AccordionTrigger>
<AccordionContent>{faq.answer}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
);
}

Contact Page

The contact page includes a form that sends emails via your configured mailer:

apps/web/src/routes/_marketing/contact.tsx

import { createFileRoute } from '@tanstack/react-router';
import { ContactForm } from '#/components/marketing/contact-form.tsx';
export const Route = createFileRoute('/_marketing/contact')({
component: ContactPage,
});
function ContactPage() {
return (
<div className="container mx-auto max-w-xl py-16">
<h1 className="text-4xl font-bold text-center mb-4">
Contact Us
</h1>
<p className="text-muted-foreground text-center mb-8">
Have a question? We'd love to hear from you.
</p>
<ContactForm />
</div>
);
}

Contact Form Configuration

Configure the recipient email address in your environment:

.env.local

CONTACT_EMAIL=support@yourdomain.com

The form submission uses your email configuration. Ensure your mailer is configured before the contact form will work.

Creating New Marketing Pages

Basic Page Structure

Create a route file. Add a new .tsx file under apps/web/src/routes/_marketing/ and set metadata with the route head option:

apps/web/src/routes/_marketing/about.tsx

import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_marketing/about')({
head: () => ({
meta: [
{ title: 'About Us | Your SaaS Name' },
{
name: 'description',
content:
'Learn about our mission, team, and the story behind our product.',
},
],
}),
component: AboutPage,
});
function AboutPage() {
return (
<div className="container mx-auto py-16">
<h1 className="text-4xl font-bold mb-8">About Us</h1>
<div className="prose prose-gray max-w-none">
<p>Your company story goes here...</p>
</div>
</div>
);
}

After adding a route file, regenerate the route tree:

pnpm --filter web generate-routes

Content-Heavy Pages

For long-form content that non-developers should be able to edit, source the content from the CMS instead of hardcoding it (see the "Dynamic Pages with Data" example below and the CMS documentation).

Dynamic Pages with Data

For pages that need dynamic data, fetch it in the route loader and render it in the component:

apps/web/src/routes/_marketing/customers.tsx

import { createFileRoute } from '@tanstack/react-router';
import { createCmsClient } from '@kit/cms';
export const Route = createFileRoute('/_marketing/customers')({
loader: async () => {
const cms = await createCmsClient();
const caseStudies = await cms.getContentItems({
collection: 'case-studies',
limit: 10,
});
return { caseStudies };
},
component: CustomersPage,
});
function CustomersPage() {
const { caseStudies } = Route.useLoaderData();
return (
<div className="container mx-auto py-16">
<h1 className="text-4xl font-bold mb-12">Customer Stories</h1>
<div className="grid md:grid-cols-2 gap-8">
{caseStudies.items.map((study) => (
<CaseStudyCard key={study.slug} {...study} />
))}
</div>
</div>
);
}

Header Navigation

Configure navigation links in the header component:

apps/web/src/components/marketing/site-navigation.tsx

const navigationItems = [
{ label: 'Features', href: '/#features' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Blog', href: '/blog' },
{ label: 'Docs', href: '/docs' },
{ label: 'Contact', href: '/contact' },
];

The footer typically includes multiple link sections:

apps/web/src/components/marketing/site-footer.tsx

const footerSections = [
{
title: 'Product',
links: [
{ label: 'Features', href: '/#features' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Changelog', href: '/changelog' },
],
},
{
title: 'Resources',
links: [
{ label: 'Documentation', href: '/docs' },
{ label: 'Blog', href: '/blog' },
{ label: 'FAQ', href: '/faq' },
],
},
{
title: 'Legal',
links: [
{ label: 'Privacy Policy', href: '/privacy-policy' },
{ label: 'Terms of Service', href: '/terms-of-service' },
{ label: 'Cookie Policy', href: '/cookie-policy' },
],
},
];

Customizing the Layout

All marketing pages inherit from the pathless layout route at apps/web/src/routes/_marketing/route.tsx. This layout includes:

  • Header with navigation
  • Footer with links
  • Shared structure via <Outlet />

Edit this file to change the shared structure across all marketing pages.

SEO for Marketing Pages

Route head option

Set SEO metadata with the route head option:

apps/web/src/routes/_marketing/pricing.tsx

export const Route = createFileRoute('/_marketing/pricing')({
head: () => ({
meta: [
{ title: 'Pricing | Your SaaS Name' },
{
name: 'description',
content:
'Choose from flexible pricing plans. Start free, upgrade when ready.',
},
{ property: 'og:title', content: 'Pricing | Your SaaS Name' },
{
property: 'og:description',
content: 'Choose from flexible pricing plans.',
},
{ property: 'og:image', content: '/images/og/pricing.png' },
],
}),
component: PricingPage,
});

Structured Data

Add JSON-LD structured data for rich search results by rendering a <script type="application/ld+json"> tag inside the route component:

function PricingPage() {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Your SaaS Name',
offers: {
'@type': 'AggregateOffer',
lowPrice: '0',
highPrice: '99',
priceCurrency: 'USD',
},
}),
}}
/>
{/* Page content */}
</>
);
}

Sitemap

Add new marketing pages to your sitemap by extending the STATIC_PATHS array in apps/web/src/routes/sitemap[.]xml.ts:

const STATIC_PATHS = [
'/',
'/pricing',
'/faq',
'/blog',
'/docs',
'/contact',
'/about', // Add new pages here
];