Paths and redirects

Preserve canonical Cavuno URLs and resolve managed redirects before returning a 404.

Use the path helpers as the single source of truth for public links. This preserves indexed URLs when moving from Cavuno’s hosted frontend and keeps cards, metadata, emails, and sitemaps aligned.

Prerequisites

  • A public board client.
  • A framework catch-all or not-found boundary.
  • A configured public origin with no path component.
ts
// src/lib/board-paths.ts
import {
blogPostPath,
boardUrl,
companyPath,
jobDetailPath,
jobsCategoryPath,
jobsLocationPath,
jobsSkillPath,
} from '@cavuno/board/paths';
const origin = 'https://jobs.example.com';
export const paths = {
company: (companySlug: string) => companyPath(companySlug),
job: (companySlug: string, jobSlug: string) =>
jobDetailPath(companySlug, jobSlug),
category: (slug: string) => jobsCategoryPath(slug),
skill: (slug: string) => jobsSkillPath(slug),
location: (slug: string) => jobsLocationPath(slug),
post: (slug: string) => blogPostPath(slug),
absoluteJob: (companySlug: string, jobSlug: string) =>
boardUrl(origin, jobDetailPath(companySlug, jobSlug)),
};

boardUrl tolerates a trailing slash on the origin and returns one absolute URL. Path helpers do not encode or validate resource slugs; pass the canonical slugs returned by the API.

Resolve a missing route

Only ask Cavuno for a managed redirect after your real route lookup misses. Guard the board-relative target before returning it to a browser.

ts
// src/data/redirects.ts
import { safeRedirectPath } from '@cavuno/board/server';
import { board } from '../lib/board';
export async function resolveMissingPath(pathname: string) {
const resolution = await board.redirects.resolve(pathname);
if (!resolution.target) return null;
const target = safeRedirectPath(resolution.target, '');
if (!target || target === pathname) return null;
return { status: 308 as const, target };
}

Return a framework 404 when the result is null. A managed match is permanent, so return HTTP 308 and preserve the request method semantics.

Resolve taxonomy aliases

Taxonomy detail methods return a canonical slug and redirectTo. For example, a company market route should wrap the returned slug in its path helper:

ts
import { companyMarketPath } from '@cavuno/board/paths';
const market = await board.companies.markets.resolve(inboundSlug);
const redirectTo = market.redirectTo
? companyMarketPath(market.redirectTo)
: null;

Use the same pattern for taxonomy and salary pages. Do not redirect to the bare slug.

Expected result

Every resource link uses one canonical SDK path. A missing route resolves to either a guarded board-relative 308 target or the framework’s normal 404 response.

Verify the result

  • A job link is exactly /companies/:companySlug/jobs/:jobSlug.
  • Moving from a hosted board preserves existing indexed paths.
  • A configured legacy path returns 308 to the board-relative target.
  • An unknown or unsafe target reaches the 404 boundary.
  • A redirect that points back to the current pathname does not loop.

Errors and edge cases

  • safeRedirectPath rejects absolute URLs, protocol-relative URLs, and parser-normalization escapes.
  • A job missing either slug does not have a canonical detail route.
  • A mutable board slug is not a substitute for a canonical resource slug.
  • Redirect resolution is public, but it should not run before a valid dynamic route lookup.

Production cautions

  • Keep one canonical origin and force HTTPS at the platform edge.
  • Test legacy URLs before changing domains or routers.
  • Never concatenate user input into an off-origin redirect.
  • Add redirect-loop monitoring and log the API request ID for unexpected failures.

Next, generate metadata and JobPosting markup with SEO and structured data.