Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Roadmap

MOTIR-4222Implemented

(motir-marketing) One canonical per page — canonical, og:url, JSON-LD @id, the per-host sitemap and robots name the primary address; every other address 301s to it, motir.co/p/* included

Opened by Zhu Yue ·

A project now answers at several addresses; this card makes exactly ONE of them the address search engines and social cards are told about, and turns every other one into a redirect. Without it the same page is indexable at motir.co/p/PROD, acme.<base>/PROD and roadmap.acme.com — three duplicates competing, which is the failure Canny's make primary exists to prevent and the story's third criterion forbids.

What ships — all in motir-marketing, over what the router and the host contract provide

  • The redirect, at page level. Every /p/* page already loads the subject (loadProject); the DTO now carries addresses.primary. Compare the request's host (the router's x-motir-public-host header, or SITE_ORIGIN's host when the router stepped aside) with the primary's host: different ⇒ permanentRedirect() to the same path on the primary, path and query preserved. This is ONE helper (lib/publicHost.tsredirectIfNotPrimary(project, request)) called from the shell so every tab and detail page inherits it — including motir.co/p/<identifier> once the project's primary lives elsewhere (ADR Q6's stated consequence).
  • The canonical and its friends name the primary: generateMetadata's alternates.canonical and openGraph.url on every /p/* page; every url / @id in app/p/[identifier]/_components/JsonLd.tsx; the changelog Atom route's <link rel="alternate"> and entry links — all built from addresses.primary through one publicUrlFor(project, path) helper, replacing every siteUrl('/p/…') call under app/p/** (a grep after this card finds none). metadataBase stays SITE_ORIGIN — an absolute canonical overrides it.
  • Per-host crawl surface. app/sitemap.ts becomes host-aware: on motir.co it lists the static entries plus only the projects whose primaryHost (the index's new field) is motir.co; on a workspace host, that workspace's projects whose primary is this host, at their host-relative paths; on a custom domain, that project's tabs at the root. app/robots.ts becomes dynamic and names THIS host's sitemap.xml and host. A sitemap may only list URLs on its own host, so a project whose canonical moved off motir.co disappears from motir.co's sitemap and appears on its own — say so in the file header.
  • The OpenGraph image route needs no change beyond og:url; confirm and say so in the PR.
  • Tests: the redirect helper for every (request host, primary host) pair including the motir.co → custom primary case and the primary is this host no-op; canonical / og:url / @id on each host kind (rendered metadata, not source grep); the Atom feed's links; the sitemap on three host kinds over a stubbed index and contract; robots per host; a grep-based guard that siteUrl('/p/ no longer appears under app/p/**.

Boundary

No routing (the router's), no new page, no change to what a page RENDERS (the story's ends at the address boundary), nothing in motir-core. The /explore cards keep linking /p/<identifier> on motir.co — the page-level redirect carries the visitor to the primary, so the directory needs no change; state this in the PR and do not touch app/explore/.

Acceptance criteria

  • On a host that is not the project's primary — motir.co, a workspace subdomain, or a non-primary custom domain — every /p/* page and tab answers 301 to the same path on the primary, query preserved; on the primary host it renders with 200.
  • The rendered <link rel="canonical">, og:url and every JSON-LD @id name the primary address on all three host kinds, asserted from rendered metadata; the Atom feed's rel="alternate" and entry links do too.
  • motir.co/sitemap.xml omits a project whose primary is elsewhere and lists one whose primary is motir.co; a workspace host's sitemap lists only its own projects at host-relative paths; a custom domain's sitemap lists that project's tabs at the root; each host's robots.txt names its own sitemap.
  • A repository grep finds no siteUrl('/p/ under app/p/** after this card.
  • No @prisma/client or database URL appears in the repository; no file outside motir-marketing is touched.

Context refs

  • motir-marketing/app/p/[identifier]/page.tsx (generateMetadata, alternates.canonical) · _components/JsonLd.tsx · changelog.xml/route.ts · opengraph-image.tsx · app/sitemap.ts · app/robots.ts · lib/siteOrigin.ts
  • the host contractaddresses.primary on the subject, primaryHost on the index; the router — the request headers this reads
  • the decision — Q6
  • MOTIR-4118 — the crawl surface as shipped for one host
  • Canny — make primary marks the canonical source (help.canny.io, fetched 2026-09-02)

Discussion

No comments yet.

Adding to this discussion signs you in on app.motir.co and brings you back to this request.

Add a comment