Patterns de routage d'URL

VérifiéSûr

Guide pour concevoir des structures d'URL SEO-friendly, générer des slugs et configurer le routage pour architectures headless CMS avec gestion des redirections et patterns multilingues.

Spar Skills Guide Bot
DeveloppementIntermédiaire
11/03/2026
Claude CodeCursorWindsurf
#url-routing#seo#slug-generation#headless-cms#redirects

Recommandé pour

Synthèse automatique

Ce skill fournit des modèles de conception d'URL (hiérarchiques, par type de contenu, hybrides) ainsi que des implémentations de génération de slugs et d'autoroutes pour une architecture de CMS headless.

Points forts

  • Couvre trois familles de structures d'URL clairement illustrées par des arborescences d'exemples
  • Fournit un service de slug complet : normalisation Unicode, suppression des diacritiques, longueur maximale et unicité par compteur
  • Explique les patterns d'autoroute à jetons ({ContentType}, {Slug}, {Category.Slug}, {Parent.Path}) et leur rendu
  • Traite aussi les URLs localisées et la gestion des redirections, souvent oubliées

Limites

  • Les exemples de code sont en C# uniquement, sans équivalent dans d'autres langages
  • Le document fourni est tronqué, la partie sur les redirections et le rendu final des autoroutes n'est pas entièrement visible
  • Aucune indication sur la gestion des collisions de slugs à grande échelle ni sur les performances de la vérification d'unicité
Quand l'utiliser

À utiliser lorsqu'on conçoit l'arborescence d'URL, la génération de slugs ou les règles de redirection d'un site piloté par un CMS headless.

Quand l'éviter

À éviter pour du routing applicatif classique (API REST, SPA) où les problématiques de SEO et de slugs de contenu ne se posent pas.

Analyse de sécurité

Sûr
Score qualité82/100

The declared allowed-tools are strictly read-only (Read, Glob, Grep, Task, Skill); no bash, write, network, or secret-handling capabilities are requested or implied. The content is pure documentation with illustrative C# snippets for slug generation, autoroute patterns, and redirects. There are no destructive commands, exfiltration, obfuscated payloads, or attempts to disable safety, so listing it carries no meaningful execution risk.

Aucun point d'attention détecté

Comment cette analyse est faite

Exemples

Design a URL structure
Design an SEO-friendly URL structure for a headless CMS site with blog posts, product categories with sub-products, and team member profiles. Show the hierarchy and justify each choice.
Unique slug service
Implement a slug service that normalizes accents, enforces a 100-character limit, and appends a counter to guarantee uniqueness per content type.
Localized routes and redirects
Plan localized URL patterns for an English/French site and describe how to handle redirects when a slug changes.

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

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/url-routing-patterns-mmlj2oj2' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/url-routing-patterns-mmlj2oj2/download' --output '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md.part' && mv '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md.part' '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Outils déclarés par l’auteur (à vérifier dans votre agent) : Read, Glob, Grep, Task, Skill

Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Après vérification, copiez le dossier complet dans le projet avec l’une des commandes ci-dessous, puis ouvrez une nouvelle session de l’outil. La commande s’arrête si un dossier du même nom existe déjà : comparez les fichiers avant de le remplacer.

Claude Code
test -f '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md' && test -s '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/url-routing-patterns' && cp -R '.skill-downloads/url-routing-patterns-mmlj2oj2' '.claude/skills/url-routing-patterns'

Cursor
test -f '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md' && test -s '.skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/url-routing-patterns' && cp -R '.skill-downloads/url-routing-patterns-mmlj2oj2' '.cursor/skills/url-routing-patterns'

OpenSkills

OpenSkills doit déjà être installé. La procédure locale install/read est contrôlée avec OpenSkills 1.5.0, qui exige Node.js ≥ 20.6.0. Aucun slug du catalogue n’est résolu automatiquement.

Installation officielle d’OpenSkills
openskills install './.skill-downloads/url-routing-patterns-mmlj2oj2'

openskills read 'url-routing-patterns-mmlj2oj2'

La lecture doit afficher le contenu du fichier installé et son dossier de base. Elle ne lance ni la skill ni ses dépendances.

Pour un agent qui lit AGENTS.md, synchronisez ensuite l’index. Cette commande modifie AGENTS.md : relisez le diff avant de l’utiliser.

openskills sync

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/url-routing-patterns-mmlj2oj2/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires