Appearance
SEO configuration
SEO helpers live in src/lib/metadata.ts. They wrap the Next.js App Router Metadata and Viewport APIs (import type { Metadata, Viewport } from "next") and pull defaults from appConfig (src/config.ts), including appName, appTagline, appDescription, domainUrl, and colors.primary.
| Helper | Purpose |
|---|---|
createMetadata(...) | Builds page metadata: title, description, keywords, authors, metadataBase, optional Open Graph + Twitter, canonical. |
getViewportMetadata() | Returns viewport: scale, theme colors, color scheme. |
WARNING
Open Graph and Twitter blocks are only added when you pass openGraph.images. If you omit images, createMetadata does not set openGraph or twitter (see implementation in src/lib/metadata.ts).
Usage in this project
Page metadata
Several routes export metadata via createMetadata, for example Terms and Privacy:
ts
import type { Metadata } from "next";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = createMetadata({
title: "Terms of Service | Your product name",
canonicalUrlRelative: "/terms",
});Other pages (login, register, root layout.tsx) use a plain export const metadata = { ... } object instead. Either pattern is valid; createMetadata is for shared defaults and OG/Twitter when you pass images.
Root layout
Currently src/app/layout.tsx defines metadata inline from appConfig and does not export viewport. To use the viewport helper app-wide, add:
ts
import { getViewportMetadata } from "@/lib/metadata";
export const viewport = getViewportMetadata();(alongside your existing metadata export).
createMetadata options
Type: createMetadataOptions (see src/lib/metadata.ts).
| Option | Type | Description |
|---|---|---|
title | string? | Page title. Default: appConfig.appTagline. |
description | string? | Meta description. Default: appConfig.appDescription. |
keywords | string[]? | Default: [appConfig.appName]. |
canonicalUrlRelative | string? | Sets alternates.canonical to this relative path. |
authors | { name: string; url?: string }[]? | Default: [{ name: appConfig.appName, url: appConfig.domainUrl }]. |
openGraph | object? | Only applied (with Twitter) when openGraph.images is present. Supports url, title, locale, siteName, description, type, images. Defaults inside that branch use appConfig for title, URL, description, and site name. |
extraTags | Record<string, string>? | Spread onto the returned metadata object for additional fields. |
Defaults always set
applicationName:appConfig.appNamemetadataBase:new URL(\${appConfig.domainUrl}/`)` — important for resolving relative image URLs in OG tags.creator:appConfig.appName
Keep appConfig.domainUrl correct in every environment so metadataBase and OG URLs resolve properly.
Example: Open Graph + Twitter
Twitter metadata uses card: "summary_large_image", reuses openGraph.images, and sets twitter.creator to "@NexusOrbit" (hardcoded in metadata.ts — change there if your handle differs).
ts
export const metadata = createMetadata({
title: "Pricing | NexusOrbit",
description: "Plans and billing for your team.",
canonicalUrlRelative: "/pricing",
keywords: ["SaaS", "Next.js", "NexusOrbit"],
openGraph: {
type: "website",
images: [
{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "NexusOrbit",
},
],
},
});Next.js convention
Comments in metadata.ts note that opengraph-image.* / twitter-image.* files under app/ are another way to supply default social images; you can use those alongside or instead of openGraph.images in code, depending on your layout split.
getViewportMetadata()
Returns a Viewport object:
| Field | Value |
|---|---|
width | "device-width" |
initialScale | 1 |
userScalable | true |
viewportFit | "cover" |
colorScheme | "light dark" |
themeColor | Two entries using appConfig.colors.primary for (prefers-color-scheme: light) and dark. |
Best practices (unchanged intent, project-specific defaults)
- Title — Short, unique per route; append brand (
| NexusOrbitorappConfig.appName) where it helps CTR. Defaults lean onappTaglinewhen omitted. - Description — Aim for concise copy;
appDescriptionis the fallback. canonicalUrlRelative— Use on primary URLs (auth/legal pages use it today on some routes — e.g./terms).metadataBase— Ensures/og-image.pngbecomes an absolute URL; fixAUTH_URL/NEXT_PUBLIC_APP_URL/ production domain if previews break.
File references
| File | Role |
|---|---|
src/lib/metadata.ts | createMetadata, getViewportMetadata |
src/config.ts | SEO-related app defaults consumed by helpers |
src/app/layout.tsx | Root metadata (inline); optional place for viewport |
src/app/(main)/terms/page.tsx, privacy-policy/page.tsx | createMetadata + canonical |
src/app/(auth)/forgot-password/page.tsx, reset-password/page.tsx | createMetadata |
The inline usage comment at the bottom of metadata.ts still mentions constructMetadata; the real export name is createMetadata.