Back to blogs

SEO Before You Ship

Check metadata, canonical URLs, social previews, crawlability, sitemaps, internal links, and missing pages before publishing your TanStack Start product.

By Antoine RossPublished
Share:

Good SEO before launch starts with three outcomes: search engines can reach the pages you want public, each page identifies one production URL, and private or missing pages stay out of search.

For a TanStack Start product, that means checking metadata, canonical URLs, social previews, crawlable content, structured data, internal links, sitemap membership, and not-found behavior. No single item is "SEO." Together, they determine whether the application presents a clear, consistent set of pages.

The standalone CoreMVP template connects those outputs to shared URL helpers and the MDX article source. It does not choose the production domain, write the final page content, set honest publication dates, or decide which custom routes should be indexable. Those are launch decisions you still own.

This is not a promise of rankings. It is the technical foundation that lets search engines discover the right pages, understand them, and represent them consistently.

The browser is only one reader. Crawlers read metadata and links. Social platforms request preview images. Sitemaps publish another site inventory. The rule for every public page is simple: its visible content, metadata, canonical, social preview, internal links, and sitemap entry should agree about what the page is and where it lives.

One page, one identity

Before you tune a title or preview, decide which URL owns the page.

Every absolute public URL needs a common origin. In the standalone template, NEXT_PUBLIC_APP_URL supplies that origin. The URL helpers use it when they build canonicals, social URLs, the sitemap location, and public sitemap entries.

The alternative is easy to get almost right: type the production hostname into a layout, repeat it in the sitemap, then use a Vercel preview URL in an image route. Each value works. Together they describe different sites.

Central URL construction removes that drift. It also creates a constraint: if the public origin is wrong, every derived URL can be consistently wrong. The configuration matters more because it is authoritative.

Canonicals sit on top of that decision. A public page can be reached through several URLs, but it should identify one preferred URL. Google treats the canonical as a signal when it chooses a representative URL; it is not a command and does not guarantee indexing or ranking. Google's canonical guidance makes that distinction explicit.

Indexability is a separate decision. A sign-in page or account dashboard should not become a public canonical owner merely because it has a URL. The template keeps public-page metadata and excluded-page metadata separate instead of forcing every route through one helper.

One application can have many pages. Every public page still needs one preferred identity.

One source, many outputs

Once the URL has an owner, the content needs one too.

In the Blog, that owner is the MDX article. Its frontmatter supplies the title, description, dates, author, and tags. Its slug determines the path. The Blog reader fans that record out to every public representation that depends on it.

If changing an article title requires editing the article, its card, its metadata, its social image, and its structured data separately, you do not have five SEO features. You have five opportunities for drift.

The native opengraph-image.ts and twitter-image.ts handlers under src/routes/_marketing/blog/$slug/ resolve the article record, then pass its title into the shared Blog composition. src/lib/blog.server.tsx publishes their URLs in the article metadata. The square card and wide sharing image stay aligned without another copy of the title.

To use static article artwork instead, store it under public/ and update the Blog metadata owner to point at its public URL. Generated images are easier to synchronize; static images give you more art direction. Keep one selected image URL authoritative for each representation.

Dates need the same discipline. The sitemap uses an article's updated date when it exists and its publication date otherwise. A deployment is not necessarily a page change. Google says lastmod should represent the last significant update and remain consistently accurate. Its sitemap guidance includes changes to primary content, structured data, or links as examples.

Structured data should also repeat visible truth, not invent a richer story. The article renderer derives BlogPosting and breadcrumb data from the article record and canonical URL. It includes fields the implementation can support truthfully. The current renderer omits the structured-data image field.

Google defines no required properties for Article markup; it recommends the properties that apply. Its Article documentation and general structured-data guidelines point toward the same rule: markup should represent the visible page.

The tradeoff is that frontmatter matters more. A wrong title or date can propagate everywhere. That is still easier to diagnose than several plausible values in different files.

Choose one article record, then make every public representation derive from it.

Give signals separate jobs

Links, sitemaps, robots.txt, and noindex all affect discovery. They are not interchangeable.

Internal links help readers and crawlers reach a page. A sitemap advertises eligible URLs. robots.txt controls crawling. noindex controls indexing.

The Blog listing and latest-article section link to published posts with real href values. That makes an article part of the site instead of an isolated URL known only by its slug. Google generally discovers standard <a href> links and recommends linking to important pages from elsewhere on the site. Its crawlable-links guidance also helps readers continue.

The sitemap publishes another view of the intentional public inventory: marketing pages, ungated documentation, and Blog articles. It can help discovery, but it cannot make a weak or contradictory page useful, canonical, or indexable by itself.

robots.txt answers a different question: which paths may compliant crawlers request? The template allows the public site and disallows /api/. It does not block the accessible HTML pages that rely on page-level noindex.

That last detail avoids a common contradiction. If you block an HTML page in robots.txt, a crawler may never retrieve the page and see its noindex directive. Google explicitly says a page must remain accessible to its crawler for noindex to be effective. The noindex guidance also warns that noindex inside robots.txt is not supported.

Authentication is another boundary again. It protects private content. A page-level indexing policy tells supporting search engines what to do with accessible HTML. Neither replaces the other.

Do not ask one SEO mechanism to perform another mechanism's job. Decide whether a page may be crawled and whether it may be indexed as two separate questions.

Missing pages refuse ownership

A missing URL has one fact to publish: the requested content does not exist.

For a Blog article, src/lib/blog.server.tsx resolves the requested slug against the MDX source and throws TanStack Router's notFound() when no article matches. The generated Open Graph and Twitter image handlers also resolve that source and return an empty 404 response for a missing article.

The missing article stays out of the Blog listing and sitemap because both come from the article collection. No separate list needs to remove it.

The native route loader resolves the article before rendering it. A direct request for a missing Blog article returns 404; the shared missing-page component supplies the missing state and noindex, without a canonical URL. Check the actual HTTP response as well as the rendered page when you change this path.

A missing URL must not become an indexable content owner.

HTTP status and search ownership are related checks. A missing article needs both the correct response and exclusion from public discovery.

Test four URL states

Before publishing, choose four representative URLs. Prove that each kind tells one coherent story.

A public product page exists and wants search ownership. Its content, self-referencing canonical, social metadata, internal links, and sitemap membership should point to the same production URL.

A published MDX article also wants ownership, but its repeated facts should come from the article record. Its rendered title, metadata, sharing image, structured data, canonical, article links, and sitemap date should agree with that source.

An excluded page exists but does not want public search ownership. Its access boundary should match the content it protects. Accessible HTML should carry the intended indexing policy, avoid presenting itself as a public canonical owner, and stay out of the public sitemap. If a crawler needs to observe noindex, robots.txt must not prevent that crawl.

A missing article does not exist. It should return 404, render the missing state, carry noindex, and stay out of the Blog listing and sitemap.

The template connects its public-origin helpers, metadata, MDX source, sharing images, structured data, sitemap, links, and missing routes. You still own the production origin, page copy and dates, public-route decisions, and each article's generated or static artwork.

You also own Search Console, analytics, distribution, redirects, RSS, and localization. The template can make a page eligible and internally consistent. It cannot guarantee crawling, indexing, ranking, a rich result, or a click.

Ask four questions before you ship:

  1. Does this URL exist?
  2. Should it own a search result?
  3. If yes, do its public representations agree on its identity?
  4. If no, do its discovery and indexing surfaces agree that it owns nothing?

Ship when every public representation agrees about what the page is, where it lives, and whether it belongs in search.

Set the final origin and product identity in Configuration, choose the Blog's generated or static social-image path in Customization, then use the Vercel deployment guide to verify the production URL before you publish.

About the author

I'm Antoine. I build CoreMVP and write about the problems I run into, the decisions I make, and what I learn along the way.