The site already has /writing/search/, and new articles are also referred to as search. These two features are unrelated within the editing interface, but they compete for the same URL. A static site does not have runtime database constraints to detect conflicts, so you must establish rules before the content is included in the build.
This document assumes that article details are marked with /writing/[slug]/, and that the project also includes categorization and pagination. The final winning page can be determined by the routing priority of the specific framework, but “winning by chance” is not a reliable product requirement. The content system should treat conflicts as errors.
First, Determine the URL’s Identity
const reserved = new Set(['search', 'tags', 'page', 'feed']);
function validateSlug(slug: string) {
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
throw new Error(`Invalid article slug: ${slug}`);
}
if (reserved.has(slug)) {
throw new Error(`Reserved article slug: ${slug}`);
}
}
An ASCII slug is not the only correct option; Chinese paths can also work. However, if using Chinese, it’s crucial to understand Unicode normalization, percent-encoding, case sensitivity, and how the editor handles copied links. Don’t assume that “browser can open” means all analysis tools, CDN rules, and link generators understand the same thing.
The above restrictions eliminate the use of underscores, consecutive hyphens, and other similar formats, making manual review easier. When migrating a website, it’s no longer possible to simply rename the old URL. Instead, the old URL should be redirected to a new URL, and it’s important to ensure that canonical URLs, RSS feeds, and internal links are consistently updated.
If the edge rule combines multiple consecutive forward slashes into one, the content system should not allow /writing//a/ and /writing/a/ to be registered separately. If the server distinguishes between uppercase and lowercase, arbitrarily converting all historical paths to lowercase could also break existing links. First, clearly define what transformations the production server performs, and then use these transformations as test inputs, rather than assuming your preferred format is a guaranteed protocol.
Encoding is particularly prone to creating two different interpretations. The percentage sequence in the original path and the decoded characters should not be mixed repeatedly; repeated decoding could cause the encoding delimiters to suddenly become actual path separators. For the slugs provided by the editor, it is recommended to use a set of allowed characters at the input boundary, and then rely on the URL API for encoding. For existing complex addresses, it is recommended to preserve the original address as the source for migration, rather than attempting to “clean” all historical paths manually using a replacement function.
Unicode also involves the distinction between visually similar characters and those with the same encoding. Two texts that look the same do not necessarily have the same character sequences. While adopting a unified standard format can reduce some differences, it will not eliminate all visually similar characters. Chinese slugs are not inherently problematic, but they require specific testing for search, copy, display, and migration. If a personal blog does not have a specific need for a Chinese path, a stable ASCII slug can reduce interpretation differences between different tools.
Verify the final path, not just the slug
If both the English and Chinese sets allow the same slug, they will not conflict unless the language prefix is explicitly specified. The program should calculate the full path, such as /en/writing/cache-key/, and use it as the de-duplication key. It is also important to consider the trailing slash strategy: /writing/a and /writing/a/ might be normalized to the same resource in the server configuration.
Pagination also expands the routing space. For example, if /writing/page/2/ is used for the second page, then page should be retained as the namespace. Also, ensure that the article slug does not contain any slashes, and rely on the router to automatically differentiate between them. Hierarchical slugs are useful, but they require more thorough path validation.
Assuming the article generator checks for duplicate slugs, and the tag generator checks for duplicate tags, both operate without errors, but generate /writing/tags/ separately. The problem isn’t that they’re missing checks, but that they’re checking against a limited range, rather than the actual available routing space. A comprehensive collision check should be performed during the planning stage, consolidating the final paths for fixed pages, content details, tags, pagination, subscription sources, and redirects. This check should also retain the generator name and source file for each record, making it easier to determine which generator is using the address.
The path registration table is not about having all modules share a large block of business code. Each generator remains responsible for its own input and path generation, submitting only standard records such as the path, output type, and source. A unified checker is responsible for identifying duplicates, preserving names, and invalid characters. This allows new search indexes to be compared with existing content simply by registering their paths. The article module does not need to know all the new features. Records without a source should be rejected, otherwise, errors would lead back to manual guessing.
It’s also important to distinguish between file paths and public paths. One deployment adapter might output /writing/a/ as writing/a/index.html, while another might use runtime routing. Checking the public path ensures that the user sees a unique identity, and checking the output path prevents file overwriting. Both may be necessary. Don’t just check if a directory exists after the build is complete, because overwritten files are already lost, and the directory still appears tidy.
A collision report must identify both sources
const paths = new Map<string, string>();
for (const article of articles) {
const path = `/writing/${article.slug}/`;
const previous = paths.get(path);
if (previous) {
throw new Error(`${path}: ${previous} conflicts with ${article.file}`);
}
paths.set(path, article.file);
}
Simply reporting “duplicate path exists” will force the editor to search individually. The output should indicate the source file, and this check should be included in both the CI and local deployment commands to ensure that the same input always results in the same failure. Do not silently retain the first file when duplicates are encountered; the order of file traversal should not determine the published content.
Calculate Edit Costs Before Designing Path Hierarchies
An address such as /writing/http/cache/key/ exposes the category, but it also turns every category change into a URL migration. If the cache article moves to the “performance” topic a year later, the author must preserve the old category path or redirect it. Stable flat slugs with independent tags often suit evolving technical notes; hierarchical paths fit structured documentation whose taxonomy changes slowly. A useful category is not necessarily a good permanent resource identifier.
If a hierarchical path structure is indeed used, each level also requires ownership. Rules should be defined for whether directory pages and article pages can have the same name, whether empty directories are created, and whether child content remains accessible after deleting a parent path. Checking the tree structure is more reliable than checking a single string: once a node is registered as an article endpoint, whether it can have child nodes again depends on the routing implementation. It is easier to fix unexpected pages that appear only at deployment time than to fix them after deployment.
Migration costs can be estimated based on external links, subscription history, and internal references, rather than just the number of files. An old directory with only three articles could still be heavily referenced by numerous documents. Review the path adjustment settings to ensure that the content identity has actually changed. Most title refinements do not require new URLs; most category changes can be resolved by the index page. Reducing unnecessary migration is simpler than maintaining a growing redirect table.
An old address is an asset to maintain.
The article title can be continuously improved, while the slug should remain as stable as possible. Automatically generating slugs from titles is convenient, but repeatedly regenerating them each time a title is changed can lead to a large number of broken links. It’s recommended to suggest a slug during the initial creation of an article and then maintain it as a separate field after saving.
Redirect tables also require cycle detection: if A points to B and B points back to A, browsers and crawlers cannot reach the content. Redirect chains should be flattened to their final destination. When deleting articles, distinguish permanent removal from migration. Do not redirect every missing article to the homepage, because that hides broken links.
When validating, create a list of paths to check for preserved routes, case sensitivity, trailing slashes, and language prefixes. Use a real deployment preview to test old URLs and verify the status codes against the Location header. It’s important to note that you can’t just test the development server, as the production edge layer may use different redirect configurations. The earlier you define the path rules, the fewer editors will need to understand the underlying routing priorities.
Prepare three independent fields for the article: Stable Internal Identifier, Readable Slug, and Editable Title. Modifying the title should not change the internal identifier or the path. Modifying the slug is a deliberate relocation and requires redirecting the old path. The internal identifier can also be used to associate comments and analysis, avoiding their dependence on the title. Do not use array indices as identifiers, as adding a new article may change the index, leading to historical relationships pointing to another record.
Assume the article changes from cache-guide to cache-key-design. The migration list should record the old URL, the final URL, and the reason for the change, and verify that the final article exists. When modifying the slug again, directly point the first old URL to the latest URL to avoid increasing the number of redirect layers. The source of the redirect should also be registered in the path: if the new article later uses the old slug, it will conflict with the historical migration, and the author must choose whether to keep the old link or reuse the name.
Do not immediately reclaim deleted names. Old links may remain active in subscriptions, emails, and external articles for years. Reusing the address can make old references appear valid while pointing to different content. Keep a registry of retired paths and return an appropriate missing response. Redirect only when a genuine replacement exists. Redirecting every missing URL to the homepage also prevents link checkers from determining whether the original content still exists.
Wildcard rules also consume addresses for files that don’t actually exist.
Assume that the output contains only /writing/search/ and normal articles, but a new /writing/* forwarding rule is added to the edge, sending the entire directory to the search service. The file list still does not have any collisions, and the articles are successfully generated, but the request handling authority has been transferred. Therefore, the path registration still needs to list the wildcard rules and their application methods, matching ranges, and priority. Checking whether specific articles fall within the rule range is more effective than simply registering the wildcard strings themselves, revealing this obfuscation more clearly.
Prepare four sample types: search entry, regular articles, attachments, and missing paths. Calculate the expected processing time for each individually, and then request verification through the preview entry. When the search service needs to manage a subdirectory, narrow the scope of the rules and avoid relying on unconfirmed assumptions like “static files should be prioritized.” If the platform truly has a fixed priority, use the corresponding documents and samples as an adapter contract. When switching deployment platforms, re-run the process; this cannot guarantee that all static sites will inherit the processing order of a single platform.
Checking for Parent-Child Conflicts in the Actual Output Tree
Assuming that the detail generator declares /writing/cache/, and the attachment generator declares /writing/cache/diagram.png, different public paths do not necessarily mean file system compatibility. If the detail output is writing/cache.html, the attachments can be placed in a directory; if a generator treats writing/cache as a file without an extension, the attachment directory cannot be created. The checker needs to know the mapping of the adapter’s output, and cannot simply perform complete string deduplication.
You can convert public paths into output nodes and register the node type (directory, file, or runtime route) in the path tree. When inserting a file, check if the same node already exists as a directory; when inserting a child node, check if the parent is a file. Conflicts with the same public address and conflicts with files using different public addresses should be reported separately. This allows the editor to determine whether the conflict is due to resource naming or whether the build configuration has selected incompatible output formats.
When checking the actual output, it’s also important to consider the target system’s case sensitivity behavior. A case-insensitive file system might overwrite two files that only differ in case before any other operations, while a Linux build machine can save both simultaneously. Furthermore, request normalization on the edge might merge them. Testing cannot simply rely on the fact that “one machine can write two files.” Applying a consistent lowercase rule to new content is straightforward, while migrating existing content requires first generating a complete list and identifying external addresses that need to be preserved.
The resources listed in the output directory should also be registered, including static files placed manually in the public area. Even a writing/search/index.html, which was not created by the current routing generator, will still occupy a deployment address. Before publishing, it’s important to merge the deployment plan with the static resources and then verify the actual tree structure. These two checks have different responsibilities: the first prevents plan conflicts, while the second detects extra files generated or misconfigured by plugins.
Defining Endpoint Constraints for Redirection Graphs
Simply checking the loop is not enough. If A points to B, and B points to a non-existent C, there is no loop, yet the system is still damaged. Each redirect source can be treated as a node in the graph, and the path can be traced along the edges to the final node. The final node must be a currently accessible resource, or a specifically approved external target. The redirect source must also not occupy a currently valid address. If the system decides to re-assign the old address as a valid one, that is a migration decision, not an automatic override for the checker.
The graph traversal must have an upper bound and should save the endpoints that have already been checked. Recursive implementations can consume the call stack when dealing with long history chains, while iterative traversal is easier to control. Error reports provide the path sequence, rather than simply stating that a node is invalid. For a normal history migration from A to B to C, deployment rules can directly point A to C; the internal migration records still retain the original change history, making it easier to explain why a particular name cannot be reused.
The handling of query parameters is governed by a separate agreement. Tracking parameters associated with older articles are retained, and typically do not affect the new main content. However, the search terms associated with the old search entry may need to be mapped to new search parameters. It is not possible to delete all old parameters uniformly, nor should user-provided next be blindly interpreted as a target destination. The path graph is responsible for resource identity, while parameter conversion is responsible for request semantics. These two aspects are tested independently, and then combined into deployment rules.
The redirect status code is not chosen arbitrarily. Permanent redirects indicate a stable new identity, while temporary redirects are used for uncertain or short-term strategies. There are differences in how different status codes handle non-GET methods. While the primary method for browsing articles is GET, if the migration rules apply to the entire site, it is not possible to assume that there are no forms or interfaces. By limiting the article rules to their namespace, we can reduce the possibility of unintended consequences affecting requests.
Re-planning When Merging Two Parallel Branches
Branch A has added the /writing/archive/ feature page, and Branch B has added an article with a slug of “archive.” Both have passed their respective base versions. After merging, it is not possible to skip the checks simply because both CI statuses are green. A complete plan must be calculated during the final merge, and the same retention rules used in production must be applied. Path uniqueness is a collective property; old conclusions cannot automatically be combined into new conclusions.
This allows change reports to display both the new paths and the new namespaces being used. When a feature page occupies an archive, it should also display the impact on future article names. Article renaming should show that the old address is registered as a redirect source. Reviewers should focus on changes in ownership, and the system should automatically check for specific equality and parent-child relationships. By combining these two functions, it can identify design issues such as “Currently, there is no name conflict, but the new rules will confuse subsequent editors.”
When reserving specific paths, it’s not necessary to include private titles in the public CI output. The internal list should only contain stable IDs and paths, while the public report should only display changes that are ready for public release. A failed reservation means the editor needs to use a different address, but it doesn’t mean the system should generate a placeholder page. If the placeholder page has value, it should be approved separately as a formal public resource, rather than as an output of a routing check.
How a loader failure becomes a link outage
After incorrectly configuring the Markdown loading directory, the build process might still succeed because the homepage and static pages exist. At this point, the path discrepancy report will show a large number of article paths disappearing. Setting a deletion threshold alert is only a supplementary measure and cannot replace explicit confirmation: deleting an entire series could also exceed the threshold. The report should list the changes in the source of the disappearing paths, distinguishing between file deletion, changes in public status, and changes in loading rules.
When deploying, choose a representative address from the “missing” list, not just the new homepage. If the platform reuses old files, some addresses may still display the old content; if the content is completely replaced, all articles may become missing pages. Both outcomes are unexpected, but the reasons are different. By keeping both the pre- and post-deployment lists, and comparing them to the deployed version, you can pinpoint whether the issue lies in the build input or the deployment output, rather than constantly modifying the slug to try and recover access.
Deploying Sub-Paths: Incorporating the Site Prefix into the Collision Model
The same site, moved from the root directory to /notes/, now has an article address of /notes/writing/cache/, but the manual resources may still be located in /writing/cache/. Currently, there is no conflict, but this does not mean that removing the prefix in the future will be safe. Path records should distinguish between internal site paths, deployment prefixes, and the final public paths, allowing the checker to understand how changes to the prefix affect specific resources, rather than blindly adding a string to all strings.
Prepare two fixed sample links: one pointing to an article generated within the site, and another pointing to the root path of an external, independent application. After switching prefixes, the first link will move with the site, while the second will remain at its original target. If both are rewritten, it indicates that the tool has lost the link’s purpose. If neither is rewritten, it suggests that the internal navigation might be jumping to a new site. This is a migration contract test, not a task for editors to manually fix paths for each article.
Testing should cover both incorrect plans and real responses.
The test intentionally introduces conflicts such as: fixed page conflicts, conflicts between tags and articles, variations in trailing slashes, and missing language prefixes. It asserts that all conflicts should fail before the output is written. The test should not rely on the order of file reads; after swapping the input order, the same set of conflicts should be produced, but the order of the source in the error message may be different. For language versions that allow the same slug, both their complete paths should be verified.
Upon deployment acceptance, requests should be made for the old path, the new path, and any non-existent paths. The status code, redirect count, and final content should be checked. The test environment should be able to follow redirects, but the Location of the first redirect should also be checked to ensure that business-related query parameters are retained. Path migration cannot directly copy unknown query parameters to untrusted destinations; if the destination comes from user input, independent open redirect protection is also required.
The final generated list can be used as a CI artifact to compare the changes made in this build, including additions, deletions, and migrations. Significant path disappearances should be manually verified, and builds should not be automatically released simply because they do not have syntax errors. This routing check not only detects the presence of two duplicate files, but also identifies configuration errors in the loader that cause an entire batch of articles to suddenly stop being included in the build.
References: Astro Routing, URI Syntax RFC 3986.