Skip to content

v0.3.1

svelte-selfheal

/the-article-title-1 looks right in Google. Your load still fetches article 1. Break the slug and you get a 301 to whatever title you have now.

Package manager

Try it

Wrong slugs 301 to /the-article-title-1. Missing IDs 404. Click one and watch the address bar.

Setup

Define healArticle once. run() or stack() in load parses the param, fetches your row, compares the slug. Canonical URL? One fetch, typed Article (or a tuple for nested routes) in result.resources. Off slug? redirect(301) before the page renders. You call error() and redirect().

Choose a setup example
src/lib/healer.ts
import { selfheal } from 'svelte-selfheal';import { getArticle } from '$lib/db/articles.js';export const healer = selfheal();export const healArticle = healer.layer({  fetch: getArticle,  segment: (article) => ({ identifier: article.id, slug: article.title })});
src/lib/healer.ts
import { selfheal } from 'svelte-selfheal';import { getArticle } from '$lib/db/articles.js';import { getAuthor } from '$lib/db/authors.js';export const healer = selfheal();export const healArticle = healer.layer({  fetch: getArticle,  segment: (article) => ({ identifier: article.id, slug: article.title })});export const healAuthor = healer.layer({  fetch: getAuthor,  segment: (author) => ({ identifier: author.id, slug: author.name })});
src/routes/[id]/+page.server.ts
import { healArticle, healer } from '$lib/healer.js';import { error, redirect } from '@sveltejs/kit';export const load = async ({ params, url }) => {  const result = await healer.run(healArticle(params.id), url.searchParams);  if (result.notFound) error(404, 'Article not found');  if (result.redirect) redirect(301, result.redirect);  const [article] = result.resources;  return { article };};
src/routes/[id]/details/[innerId]/+page.server.ts
import { healArticle, healAuthor, healer } from '$lib/healer.js';import { error, redirect } from '@sveltejs/kit';export const load = async ({ params, url }) => {  const result = await healer.stack(    [healArticle(params.id), 'details', healAuthor(params.innerId)],    url.searchParams  );  if (result.notFound) error(404, 'Not found');  if (result.redirect) redirect(301, result.redirect);  const [article, author] = result.resources;  return { article, author };};

Browse examples →

How it works

parseId() from the param, fetch by ID, build the canonical slug, compare. Mismatch sends a 301.

One request

Page still loads from the ID — then one 301 to the single canonical path.

Where this comes from

Aaron Francis walked through self-healing URLs on Laravel — pretty slug, stable ID at the end, 301 when the slug rots.

Self-healing URLs in Laravel Watch Self-healing URLs in Laravel on YouTube (opens in new tab)

Also, at this point: shoutout Aaron for all the cool shit he's put out. This repo wouldn't exist without that video.

Defaults

Hyphen IDs and kebab slugs out of the box. Pass identifier and sanitize to selfheal() when your routes use something else.

Parse ID

identifier.separate(param) in parseId()

  • HyphenIdentifierHandler my-article-title-1 → id 1
  • TildeIdentifierHandler my-article~a1b2-c3d4 → id a1b2-c3d4

Build canonical

sanitize(slug) + identifier.join() in createUrl()

  • KebabSlugSanitizer "My Fancy Title" → my-fancy-title
  • SnakeSlugSanitizer "My Fancy Title" → my_fancy_title