GenerateSaaS

Marketing & SEO

Assemble the prerendered landing surface and emit structured data, canonical and hreflang links, sitemap, and robots from config.

The marketing surface is driven by @repo/config - the config.seo block, the config.indexable master switch, and config.business / config.social for structured data. Every SEO artifact is derived from those keys, so config is the single source of truth.

Landing & marketing pages

The landing page (app/[locale]/(marketing)/page.tsx) composes section components from components/landing/ in this order: Hero, StatsRow, DemoVideo, Showcase, FeaturesListicle, HowItWorks, ProblemSolution, TestimonialsGrid, Pricing, PricingComparison, PricingProducts, FAQ, FinalCta, plus a FaqJsonLd node.

RoutePurpose
/Landing page, pricing included at the #pricing anchor
/contactContact form
/terms, /privacy, /cookies, /refundLegal pages

There is no separate pricing page. Plans render from pricingConfig.plans inside the landing page's #pricing section, and next.config.mjs permanently redirects /pricing to /#pricing. Link to plans with config.routes.pricing, never a literal path.

Editing landing copy

All visible copy is i18n - there is no hardcoded marketing text.

  • Section copy lives under landing.* keys and the landing <title> / meta description under home.meta, both in packages/i18n/translations/en/web.json. Only edit en; other locales are generated.
  • Hero avatars are the one shared data export, socialProofAvatars in packages/config/src/landing.ts - five files self-hosted under public/avatars/. Replace the array and the .webp files when rebranding.
  • Section-aware footer and navbar: an item carrying a section drops out when that section has no content for the locale - see Content sections.
  • Footer attribution: config.builtWith renders "Built by {founderName} using GenerateSaaS". Clear founderName for the shorter line, or delete the block - see configuration.

Structured data

Every node is emitted with the <JsonLd> component (React-19-safe inline application/ld+json), cross-linked by @id into one graph.

JSON-LD nodeEmitted onBuilt from
Organization + WebSiteevery pageconfig.business, config.social, config.seo, config.logo
WebPage (+ BreadcrumbList)WebPage on every page; a BreadcrumbList joins it on content pages, contact, legal and /download, each of which also renders the trail on screenpage title + description
Content-page graphevery content document, listing and hubSEO kit
FAQPage/faqItems (i18n)
SoftwareApplication (+ AggregateRating + Review when opted in)/pricingConfig + lib/landing/testimonials.ts
Source keyFeeds
config.business?.name (into config.siteName)JSON-LD name / legalName - a placeholder, edit before launch
config.baseUrlJSON-LD url, sitemap entries
config.logo?.mainJSON-LD logo
config.seo?.foundingDateJSON-LD foundingDate (optional)
config.socialJSON-LD sameAs array; twitter:site derives from config.social?.x
config.seo.descriptionOrganization JSON-LD description + the RSS channel of every article section, not the page <meta description>

AggregateRating and Review are off unless you set config.seo.productRating: true. They are built from lib/landing/testimonials.ts, which ships placeholder reviews - Google ignores reviews a business writes about itself and publishing them risks a manual action. Turn the option on only once the ratings are real.

The indexable switch

config.indexable is the master switch behind app/robots.txt/route.ts and app/sitemap.ts.

Staterobots.txtsitemap
true (production)Allow: /, the AI crawler groups, plus a sitemap pointerOne entry per route and locale
false (demos, staging)Disallow: /Empty

The named AI crawler groups and Content Signals in that file come from config.agents.crawlers - see AI Agents & Crawlers.

Dashboard, admin, auth, every section's /search, tag archives, the ?sort= variants of an article archive, and /unsubscribe carry a per-route noindex meta tag (Metadata.robots) rather than a robots.txt Disallow, so crawlers read the directive and drop the page cleanly. A section with content puts its pages in the sitemap, adds an RSS <link rel="alternate"> to <head> when it is an article section, and the first article section gives the WebSite node its SearchAction.

On this page