DEV Community

member_5432fd74
member_5432fd74 Subscriber

Posted on

Adding Categories to a Live Directory Without Breaking Every URL You Already Rank For

Adding a category to a directory nobody visits is a schema change. Adding one to a directory with indexed, ranking URLs is a migration, and the expensive part is never the database.

The trap is that the obvious route design forces a rename of everything already published.

The two route shapes

Most directories start with a route per category, because there were only two:

/schools/[state]/[city]/[slug]
/therapy/[state]/[city]/[slug]
Enter fullscreen mode Exit fullscreen mode

This is fine at two. At six it means six near-identical route files, six sitemap generators, and six places to fix any routing bug. The instinct is to collapse them:

/providers/[category]/[state]/[city]/[slug]
Enter fullscreen mode Exit fullscreen mode

Cleaner, and it makes adding a category free. It also renames every URL you already rank for, and that is not a free trade. Redirects pass most signal, not all of it, and a site-wide 301 during an algorithm-sensitive period is the kind of change you cannot cleanly attribute later when traffic moves.

Keep the existing top-level paths. Add new categories as new top-level paths. The duplication is real but bounded, and it is cheaper than a rename. Factor the shared logic into one resolver that every route calls:

// src/lib/provider-route.ts
export async function resolveProviderPage(category: string, params: RouteParams) {
  const provider = await fetchProvider({ category, ...params });
  if (!provider) return null;
  return {
    provider,
    canonical: buildCanonical(category, provider),
    breadcrumbs: buildBreadcrumbs(category, provider),
  };
}
Enter fullscreen mode Exit fullscreen mode

Each route file becomes a thin wrapper that names its category and delegates. Adding a category is a new file of about ten lines, and routing bugs are fixed once.

Failure mode one: the slug that is not the slug

If your detail URLs are derived from the provider name rather than a stored slug column, moving a provider between categories changes its path. If they are derived from a stored slug that admins can edit, an innocuous rename silently 404s a ranking page.

Pick one, write it down, and make the resolver 301 anything that does not match canonical:

const canonicalPath = buildCanonical(category, provider);
if (Astro.url.pathname !== canonicalPath) {
  return Astro.redirect(canonicalPath, 301);
}
Enter fullscreen mode Exit fullscreen mode

That single guard turns every near-miss URL, from a stale link, an old sitemap, a category move, into a redirect instead of a 404. It is five lines and it is the highest-value thing in this post.

Failure mode two: the empty hub

A category route that renders with zero results is worse than no route. It is thin content, it gets crawled, and it teaches search systems that the path is low value before you have populated it.

Gate the route on having something to show:

export async function getStaticPaths() {
  const categories = await fetchCategories();
  const withInventory = await Promise.all(
    categories.map(async (c) => ({ c, count: await countProviders(c.slug) }))
  );
  return withInventory
    .filter(({ count }) => count >= MIN_LISTINGS_PER_HUB)
    .map(({ c }) => ({ params: { category: c.slug } }));
}
Enter fullscreen mode Exit fullscreen mode

The category can exist in the database, in the admin, and in the taxonomy long before it exists as a public route. Decoupling those is what lets you seed inventory quietly and launch the page once it is worth landing on.

Ship order

  1. Add the category to the taxonomy. Nothing public changes.
  2. Seed providers. Still nothing public.
  3. Route becomes eligible once it crosses the inventory threshold.
  4. Add it to navigation and the sitemap, in that order.
  5. Submit the new URLs and watch coverage, not rankings, for the first two weeks.

Steps three and four are the ones people merge, and merging them is how you end up with a nav link to an empty page.


I work on Special Needs Care Network, a directory of ABA therapy providers and special education schools across all 50 US states. The route and inventory-gate patterns above are what we use when the category list grows.

Top comments (0)