SEO metadata and social sharing
Use the starter's metadata helper, canonical origin, social image and structured data for real public pages.
Start with the shared defaults
Metadata describes a page to browsers, search engines and link previews. The starter centralizes brand identity in config/site.ts and discovery settings in config/seo.ts. Review the site name, description, language, locale and social-image path when you replace the sample marketing copy.
Metadata is not authorization and does not make an unfinished product ready to index. Keep SEO_INDEXABLE=false while customizing. The indexing guide explains the production switch and development/preview guards.
The root layout intentionally does not assign the homepage canonical URL to every route. A canonical identifies the preferred URL for one public page. Applying the home URL to unknown or private pages would misdescribe those pages.
Set the canonical origin
Use your real HTTPS origin in active APP_URL, with no path, embedded credentials, query or fragment. APP_URL_DEV and APP_URL_LIVE are saved references; they do not independently select the current runtime origin. Deployment installs the verified live origin for the production runtime.
The SEO helpers derive URLs from that configured origin, not request Host headers. A development preview can therefore display production-looking canonicals if you configured it that way; it must still remain noindex. Check both the URL and indexability rather than assuming one proves the other.
Changing a production origin also affects authentication, email callbacks and MCP audiences. Follow domains and deployment recovery instead of treating this as a title-only edit.
Add a real public page with publicMetadata
The following complete page describes the optional notes feature after you implement it. Create app/features/notes/page.tsx in your product copy, and edit its claims to match your actual behavior:
import Link from 'next/link';
import { publicMetadata } from '@/lib/seo/metadata';
export const metadata = publicMetadata({
title: 'Workspace notes',
description: 'Read short team notes in one workspace. Owners and admins can add a note.',
path: '/features/notes',
});
export default function NotesFeaturePage() {
return <main className="mx-auto max-w-3xl space-y-6 px-6 py-16">
<h1 className="text-4xl font-semibold">Workspace notes</h1>
<p>Keep a decision and its next action where your teammates can find them.</p>
<h2 className="text-2xl font-semibold">A clear workspace boundary</h2>
<p>Members read the latest 50 notes. Workspace owners and admins create them.</p>
<Link href="/login" className="underline">Sign in to your workspace</Link>
</main>;
}Then add { path: '/features/notes' } to the existing pages array in config/seo.ts. Keep its current entries. Register only an implemented public HTML route; do not add a redirect, query variant, private dashboard route or a promise of a future feature.
publicMetadata supplies the complete title, description, canonical, robots, Open Graph and Twitter objects. Its full title is the page title followed by the site name. Next.js metadata objects merge shallowly, so using the shared helper avoids accidentally dropping the default image when changing one Open Graph field.
Open Graph, Twitter and images
The default social image is the generated /social-image route: a 1200 × 630 PNG using the site's configured brand. Open it directly after rebranding. Check that long names fit and the image is legible at a small preview size. Replace app/favicon.ico with your own icon and verify the actual browser tab, not only the asset file.
To give one page a specific social image, add a reviewed repository asset such as public/social/notes.png and pass image: '/social/notes.png' into the helper. The image must resolve publicly at your deployment's origin. A private Storage URL or expiring signed link is unsuitable for persistent sharing metadata.
Inspect og:title, og:description, og:url, og:image and twitter:card in the rendered HTML. Confirm image URLs are absolute, HTTPS on production, and return an image content type. Social services may cache old metadata. Verify the deployed response first, then use the service's own re-scrape/debug tooling when available. A locally rendered image does not prove a social platform fetched it.
Truthful JSON-LD
JSON-LD is structured data embedded in an HTML script. The starter's components/seo/json-ld.tsx escapes < before embedding serialized JSON, preventing text like a closing script tag from breaking out of the script. Keep that escaping if you extend the component.
The homepage describes the real website. Blog pages produce BlogPosting data from their visible author, title, excerpt, publication date and image. Docs and stories include breadcrumbs. Do not add invented reviews, ratings, prices or organization addresses for richer-looking search results. Structured data must agree with visible content and the actual business.
For a feature page, a truthful page title and description are often enough. Additional schema types should be selected for a real requirement and checked against the relevant search engine's official rules. See Next.js JSON-LD guidance for the embedding pattern.
Blog and documentation metadata
Product documentation titles and descriptions come from MDX frontmatter. A useful description tells the reader what they will accomplish, rather than repeating the title. Keep headings stable where people already link to an anchor.
Published blog metadata comes from the same public reader as the article. Drafts and future publications must remain absent from HTML and discovery. Blog listing pagination has its own canonical per page. Never advertise the first page's canonical for every paginated result or fabricate modification dates when no reliable date exists.
Verify the page and social preview
On your running app, open the new route, use View Source or browser tools to inspect metadata, open its image URL and test the mobile page. These POSIX commands inspect public outputs without application credentials:
curl --fail-with-body http://localhost:3000/features/notes
curl --head http://localhost:3000/social-image
curl --fail-with-body http://localhost:3000/sitemap.xmlIn PowerShell, use curl.exe for these commands. The local sitemap may correctly contain no URLs while indexing is disabled. Run the existing SEO fixture and browser checks listed in testing, then repeat against the actual production origin before enabling discovery. Local checks cover generated metadata; remote social-crawler acceptance remains a separate observation.
Template 0.0.1 · September 2026 source snapshot