Claude Code Plugins

Community-maintained marketplace

Feedback

Use when designing URL structures, slug generation, SEO-friendly URLs, redirects, or localized URL patterns. Covers route configuration, URL rewriting, canonical URLs, and routing APIs for headless CMS.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name url-routing-patterns
description Use when designing URL structures, slug generation, SEO-friendly URLs, redirects, or localized URL patterns. Covers route configuration, URL rewriting, canonical URLs, and routing APIs for headless CMS.
allowed-tools Read, Glob, Grep, Task, Skill

URL Routing Patterns

Guidance for designing URL structures, slug generation, and routing strategies for headless CMS architectures.

When to Use This Skill

  • Designing SEO-friendly URL structures
  • Implementing slug generation
  • Configuring redirect management
  • Planning localized URL patterns
  • Building routing APIs

URL Structure Patterns

Hierarchical URLs (Page-Based)

/                           # Home
/about                      # About page
/about/team                 # Team (child of About)
/about/team/leadership      # Leadership (grandchild)
/products                   # Products listing
/products/software          # Software category
/products/software/crm      # Specific product

Content Type URLs (Collection-Based)

/blog                       # Blog listing
/blog/2025/01/my-article    # Blog post with date
/blog/my-article            # Blog post without date

/docs                       # Documentation home
/docs/getting-started       # Doc section
/docs/getting-started/install # Doc page

/team/jane-doe              # Team member profile
/portfolio/project-alpha    # Portfolio item

Hybrid URLs

/products/software/crm      # Category > Product
/blog/technology/ai-trends  # Category > Post
/help/faq/billing           # Section > Topic

Slug Generation

Slug Service

public class SlugService
{
    public string GenerateSlug(string text, SlugOptions? options = null)
    {
        options ??= new SlugOptions();

        var slug = text
            .ToLowerInvariant()
            .Normalize(NormalizationForm.FormD);

        // Remove diacritics
        slug = new string(slug
            .Where(c => CharUnicodeInfo.GetUnicodeCategory(c)
                != UnicodeCategory.NonSpacingMark)
            .ToArray());

        // Replace spaces and invalid chars
        slug = Regex.Replace(slug, @"[^a-z0-9\s-]", "");
        slug = Regex.Replace(slug, @"\s+", "-");
        slug = Regex.Replace(slug, @"-+", "-");
        slug = slug.Trim('-');

        // Enforce max length
        if (slug.Length > options.MaxLength)
        {
            slug = slug.Substring(0, options.MaxLength).TrimEnd('-');
        }

        return slug;
    }

    public async Task<string> GenerateUniqueSlugAsync(
        string text,
        string contentType,
        Guid? excludeId = null)
    {
        var baseSlug = GenerateSlug(text);
        var slug = baseSlug;
        var counter = 1;

        while (await SlugExistsAsync(slug, contentType, excludeId))
        {
            slug = $"{baseSlug}-{counter}";
            counter++;
        }

        return slug;
    }
}

public class SlugOptions
{
    public int MaxLength { get; set; } = 100;
    public bool AllowUnicode { get; set; } = false;
    public string Separator { get; set; } = "-";
}

Autoroute Patterns

public class AutorouteSettings
{
    public string Pattern { get; set; } = string.Empty;
    public bool AllowCustom { get; set; } = true;
    public bool ShowHomepageOption { get; set; }
}

// Pattern examples:
// "{ContentType}/{Slug}"           -> /article/my-title
// "{Category.Slug}/{Slug}"         -> /technology/my-article
// "blog/{CreatedUtc.Year}/{Slug}"  -> /blog/2025/my-article
// "{Parent.Path}/{Slug}"           -> /about/team/leadership

public class AutorouteService
{
    public string GeneratePath(ContentItem item, string pattern)
    {
        var path = pattern;

        // Replace tokens
        path = path.Replace("{Slug}", item.Slug);
        path = path.Replace("{ContentType}", item.ContentType.ToLower());
        path = path.Replace("{CreatedUtc.Year}", item.CreatedUtc.Year.ToString());
        path = path.Replace("{CreatedUtc.Month}",
            item.CreatedUtc.Month.ToString("00"));

        // Handle relationships
        if (path.Contains("{Category.Slug}") && item.CategoryId.HasValue)
        {
            var category = _categoryRepository.Get(item.CategoryId.Value);
            path = path.Replace("{Category.Slug}", category?.Slug ?? "uncategorized");
        }

        // Handle parent path
        if (path.Contains("{Parent.Path}") && item.ParentId.HasValue)
        {
            var parent = _contentRepository.Get(item.ParentId.Value);
            path = path.Replace("{Parent.Path}", parent?.Path ?? "");
        }

        // Normalize path
        path = "/" + path.Trim('/').ToLowerInvariant();
        return path;
    }
}

Redirect Management

Redirect Types

public class Redirect
{
    public Guid Id { get; set; }
    public string FromPath { get; set; } = string.Empty;
    public string ToPath { get; set; } = string.Empty;
    public RedirectType Type { get; set; }
    public bool IsRegex { get; set; }
    public bool PreserveQueryString { get; set; }
    public DateTime? ExpiresUtc { get; set; }
}

public enum RedirectType
{
    Permanent = 301,    // Moved permanently (SEO transfers)
    Temporary = 302,    // Found (temporary redirect)
    SeeOther = 303,     // See other (POST to GET)
    TemporaryRedirect = 307, // Temporary (preserves method)
    PermanentRedirect = 308  // Permanent (preserves method)
}

Automatic Redirect on Slug Change

public class ContentUpdateHandler
{
    public async Task HandleSlugChangeAsync(
        Guid contentId,
        string oldPath,
        string newPath)
    {
        if (oldPath == newPath) return;

        // Create redirect from old to new
        var redirect = new Redirect
        {
            Id = Guid.NewGuid(),
            FromPath = oldPath,
            ToPath = newPath,
            Type = RedirectType.Permanent,
            PreserveQueryString = true
        };

        await _redirectRepository.AddAsync(redirect);

        // Update any existing redirects pointing to old path
        var existingRedirects = await _redirectRepository
            .GetByToPathAsync(oldPath);

        foreach (var existing in existingRedirects)
        {
            existing.ToPath = newPath;
            await _redirectRepository.UpdateAsync(existing);
        }
    }
}

Localized URLs

URL Localization Strategies

Strategy Example Pros Cons
Path prefix /en/about, /fr/about Clear, SEO-friendly Longer URLs
Subdomain en.site.com, fr.site.com Separate hosting Complex setup
Query param /about?lang=fr Simple Poor SEO
Translated slugs /about, /a-propos Natural Hard to manage

Path Prefix Implementation

public class LocalizedRoutingService
{
    private readonly string[] _supportedLocales = { "en", "fr", "de", "es" };
    private readonly string _defaultLocale = "en";

    public string GetLocalizedPath(string path, string locale)
    {
        // Remove existing locale prefix
        var cleanPath = RemoveLocalePrefix(path);

        // Add new locale prefix (skip for default)
        if (locale != _defaultLocale)
        {
            return $"/{locale}{cleanPath}";
        }

        return cleanPath;
    }

    public (string path, string locale) ParseLocalizedPath(string requestPath)
    {
        foreach (var locale in _supportedLocales)
        {
            if (requestPath.StartsWith($"/{locale}/") ||
                requestPath == $"/{locale}")
            {
                var path = requestPath.Substring(locale.Length + 1);
                return (string.IsNullOrEmpty(path) ? "/" : path, locale);
            }
        }

        return (requestPath, _defaultLocale);
    }
}

Hreflang Tags

public class HreflangService
{
    public List<HreflangTag> GenerateHreflangTags(
        ContentItem content,
        string baseUrl)
    {
        var tags = new List<HreflangTag>();

        // Get all localized versions
        var localizations = _localizationService
            .GetLocalizedVersions(content.Id);

        foreach (var loc in localizations)
        {
            tags.Add(new HreflangTag
            {
                Hreflang = loc.Locale,
                Href = $"{baseUrl}{GetLocalizedPath(content.Path, loc.Locale)}"
            });
        }

        // Add x-default
        tags.Add(new HreflangTag
        {
            Hreflang = "x-default",
            Href = $"{baseUrl}{content.Path}"
        });

        return tags;
    }
}

public class HreflangTag
{
    public string Hreflang { get; set; } = string.Empty;
    public string Href { get; set; } = string.Empty;
}

Canonical URLs

public class CanonicalUrlService
{
    public string GetCanonicalUrl(HttpRequest request, ContentItem content)
    {
        var baseUrl = $"{request.Scheme}://{request.Host}";

        // Use content's primary path as canonical
        var canonicalPath = content.PrimaryPath ?? content.Path;

        // Remove query parameters (unless paginated)
        // Normalize trailing slash

        return $"{baseUrl}{canonicalPath}";
    }
}

URL Normalization

public class UrlNormalizer
{
    public string Normalize(string url, NormalizationOptions options)
    {
        var uri = new UriBuilder(url);

        // Lowercase path
        uri.Path = uri.Path.ToLowerInvariant();

        // Handle trailing slash
        if (options.TrailingSlash == TrailingSlashBehavior.Remove)
        {
            uri.Path = uri.Path.TrimEnd('/');
        }
        else if (options.TrailingSlash == TrailingSlashBehavior.Add &&
                 !uri.Path.EndsWith('/'))
        {
            uri.Path += '/';
        }

        // Sort query parameters
        if (options.SortQueryParams && !string.IsNullOrEmpty(uri.Query))
        {
            var queryParams = HttpUtility.ParseQueryString(uri.Query);
            var sorted = queryParams.AllKeys
                .OrderBy(k => k)
                .Select(k => $"{k}={queryParams[k]}");
            uri.Query = string.Join("&", sorted);
        }

        return uri.ToString();
    }
}

public class NormalizationOptions
{
    public TrailingSlashBehavior TrailingSlash { get; set; }
    public bool SortQueryParams { get; set; }
    public bool ForceLowercase { get; set; } = true;
}

public enum TrailingSlashBehavior
{
    Remove,
    Add,
    Preserve
}

Routing API

Endpoints

GET /api/routes/resolve?path=/about/team  # Resolve path to content
GET /api/redirects                        # List redirects
GET /api/sitemap.xml                      # XML sitemap
POST /api/slugs/generate                  # Generate slug from text
POST /api/slugs/validate                  # Check slug availability

Route Resolution Response

{
  "data": {
    "path": "/about/team",
    "contentId": "page-456",
    "contentType": "Page",
    "locale": "en",
    "canonical": "https://example.com/about/team",
    "alternates": [
      { "hreflang": "fr", "href": "https://example.com/fr/a-propos/equipe" },
      { "hreflang": "de", "href": "https://example.com/de/uber-uns/team" }
    ],
    "breadcrumbs": [
      { "label": "Home", "path": "/" },
      { "label": "About", "path": "/about" },
      { "label": "Team", "path": "/about/team" }
    ]
  }
}

Related Skills

  • page-structure-design - Page hierarchy for URLs
  • navigation-architecture - Menu links and paths
  • headless-api-design - Routing API endpoints