Repo: motir-core. Found 2026-08-09 in production logs, during MOTIR-2392's cutover.
Emitted by the running app on Fly, on every render of a page carrying metadata:
⚠ metadataBase property in metadata export is not set for resolving social open graph
or twitter images, using "http://localhost:8080"
Next resolves relative openGraph.images / twitter.images URLs against metadataBase. With it unset, Next falls back to the dev origin — so the absolute URL emitted into <meta property="og:image"> points at http://localhost:8080, which no crawler, social card renderer or link-preview fetcher can reach.
$ grep -rn "metadataBase" app/ lib/ next.config.ts
(no matches — set nowhere in the repository)
app/layout.tsx:96-99 is the whole root metadata export:
export const metadata: Metadata = {
title: 'Motir',
description: 'AI-native project management — open-source PM substrate.',
};
It undermines work already shipped. MOTIR-1150 built two OG image routes to the brand spec — app/(public)/explore/opengraph-image.tsx and app/(public)/p/[identifier]/opengraph-image.tsx, each rendering the real wave-band mark through ImageResponse. Those images generate correctly and are then advertised at an address nobody outside the container can fetch.
The affected surface is the whole crawlable public set built by Stories 6.12/6.13: /explore, its rank variants, /explore/topic/<slug>, and /p/<identifier> plus its board/items/roadmap tabs. Every one of them is meant to be shared and indexed.
This is PRE-EXISTING and was not caused by the hosting move — it would have been equally wrong on Vercel. Two things make it worth carding now: the public surface is live on a new origin, and MOTIR-1130 ("Take Motir's project public + smoke the public surfaces") is going to walk exactly these pages.
Set metadataBase from the origin accessor the app already owns:
import { resolveBaseUrlTrimmed } from '@/lib/baseUrl';
metadataBase: new URL(resolveBaseUrlTrimmed()),
lib/baseUrl.tsapp/layout.tsx sits in every route's module graph, so anything it imports is traced into every server function Next builds. That file's own docstring records the measurement (MOTIR-2381): 340 of 348 traced functions already carry @prisma/client, and lib/db.ts instantiates a client at module scope.
lib/baseUrl.ts has zero imports — a pure environment reader — so it is safe. lib/publicProjects/urls.ts's publicSiteOrigin() is a one-line delegate to the same function and is also light today, but it is a domain module free to grow imports later. Import the leaf.
metadata export can BAKE the localhost fallbackexport const metadata is evaluated when the module is loaded, which for statically-rendered routes means at build time — and the image build deliberately runs without MOTIR_BASE_URL (MOTIR-2490 sets only inert placeholders), so resolveBaseUrlTrimmed() would return the localhost fallback and Next would freeze it into the output. The build would look clean and ship the same bug.
The build generates 177 static pages, so this is not hypothetical. Prefer export async function generateMetadata(), which is evaluated per request with the real runtime environment — or, if the static export is kept, prove by fetching a built page that the baked value is the production origin and not localhost.
metadataBase is set, sourced from lib/baseUrl.ts, and not hardcoded.<meta property="og:image"> is absolute against the production origin, with no occurrence of localhost in any og: or twitter: URL.⚠ metadataBase … is not set warning no longer appears in the Fly logs.@prisma/client reach. scripts/measure-prisma-traces.mjs is the existing instrument; the traced-function count must not rise.The metadata base URL only. It does not redesign the OG images, change the brand mark, touch sitemap.ts or robots, or alter canonical-URL logic — publicProjectUrl() already builds absolute canonicals correctly through the same accessor, which is why canonicals were never affected and only the image URLs are wrong.
app/layout.tsx:96-99 — the metadata export to amend, and its docstring on why the import graph is load-bearing.lib/baseUrl.ts — resolveBaseUrl() / resolveBaseUrlTrimmed(), the zero-import leaf to use.lib/publicProjects/urls.ts:15 — publicSiteOrigin(), the sibling accessor, and why canonical URLs are already right.app/(public)/explore/opengraph-image.tsx · app/(public)/p/[identifier]/opengraph-image.tsx — the images being mis-addressed.