Skip to content

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.

HelperPurpose
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).

OptionTypeDescription
titlestring?Page title. Default: appConfig.appTagline.
descriptionstring?Meta description. Default: appConfig.appDescription.
keywordsstring[]?Default: [appConfig.appName].
canonicalUrlRelativestring?Sets alternates.canonical to this relative path.
authors{ name: string; url?: string }[]?Default: [{ name: appConfig.appName, url: appConfig.domainUrl }].
openGraphobject?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.
extraTagsRecord<string, string>?Spread onto the returned metadata object for additional fields.

Defaults always set

  • applicationName: appConfig.appName
  • metadataBase: 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:

FieldValue
width"device-width"
initialScale1
userScalabletrue
viewportFit"cover"
colorScheme"light dark"
themeColorTwo 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 (| NexusOrbit or appConfig.appName) where it helps CTR. Defaults lean on appTagline when omitted.
  • Description — Aim for concise copy; appDescription is the fallback.
  • canonicalUrlRelative — Use on primary URLs (auth/legal pages use it today on some routes — e.g. /terms).
  • metadataBase — Ensures /og-image.png becomes an absolute URL; fix AUTH_URL / NEXT_PUBLIC_APP_URL / production domain if previews break.

File references

FileRole
src/lib/metadata.tscreateMetadata, getViewportMetadata
src/config.tsSEO-related app defaults consumed by helpers
src/app/layout.tsxRoot metadata (inline); optional place for viewport
src/app/(main)/terms/page.tsx, privacy-policy/page.tsxcreateMetadata + canonical
src/app/(auth)/forgot-password/page.tsx, reset-password/page.tsxcreateMetadata

The inline usage comment at the bottom of metadata.ts still mentions constructMetadata; the real export name is createMetadata.

Built with Nexus Orbit