Are flat Destination and Location URLs sensible for production or too much custom routing?
I posted here a bit over a week ago about using flat URLs for Destinations and Locations on a travel site I’m developing, while keeping Activities under /activity/.
The structure I'm leaning towards is:
/queenstown/ Destination CPT post
/otago/ Otago term in the Location taxonomy
/activity/shotover-jet/ Activity CPT post
Internally the geography can still be hierarchical, for example:
South Island
→ Otago
→ Queenstown Lakes
→ Queenstown
→ Shotover Jet
The hierarchy would be handled through taxonomies, breadcrumbs and navigation rather than being built into the public URL.
A few people on my original post raised good questions about collisions, WordPress's own root-level routes, canonicals and long-term maintenance, so I spent some time testing those rather than just assuming the resolver would be fine.
The current test version uses one cached registry that maps each valid root slug directly to its WordPress object. For example:
queenstown → Destination post ID
otago → Location term ID
about → Page ID
Activities do not use that resolver and continue through normal WordPress routing under /activity/{slug}/.
I tested quite a few failure cases as well:
- Unknown and malformed root URLs return genuine 404s.
- A Destination and Location cannot silently share the same flat slug. The route is withheld if there is a collision.
- Draft, Publish, Trash, Restore and slug changes automatically update the registry.
/activity/is protected as a reserved root.- I also explicitly tested
/feed/,/search/,/page/and/wp-json/. - If a Destination or Location tries to claim one of those reserved roots, the resolver refuses to publish that route and the native WordPress functionality continues to work.
/feed/continued serving the RSS feed./search/test/continued serving WordPress search results./wp-json/and/wp-json/wp/v2/postscontinued serving the REST API.
That testing exposed something I hadn't really considered before.
WordPress can still allow content to have one of those reserved slugs and generate a View/permalink for it even though the resolver refuses to publish the route.
For example, I created a Location with the slug feed. The resolver correctly blocked it from /feed/, so the RSS feed continued working, but WordPress still showed /feed/ as that Location's View URL.
The same thing happened with search, page and wp-json.
For production, would you prevent an editor from publishing a root-level Page, Destination or Location with a reserved slug in the first place, rather than just detecting the conflict and withholding the route? And would you show the editor a clear message explaining why that slug is unavailable?
I also tested the permalink/canonical/sitemap side.
For a Destination:
/queenstown/
The browser URL, WordPress View URL, resolver path, HTML canonical and WordPress XML sitemap URL all matched.
For an Activity:
/activity/shotover-jet/
All of those matched as well.
For a Location:
/otago/
The browser URL, WordPress term URL, resolver path and XML sitemap URL all matched. The one difference was that vanilla WordPress did not output an actual <link rel="canonical"> tag for the taxonomy archive, so I'd want to make sure the eventual SEO setup supplies exactly one canonical for /otago/.
The native WordPress sitemaps also came out as I hoped:
/queenstown/
/otago/
/activity/shotover-jet/
At this point I'm reasonably confident the routing itself can be made to work. Would experienced WordPress developers actually be comfortable maintaining something like this for years?
If you were building this for production, how would you handle the reserved root namespace over time? Would you keep an explicit list of protected roots, derive them from WordPress rewrite rules somehow, or use another approach?
If the reserved-slug validation, canonicals and sitemaps are handled properly, would you be comfortable keeping the flat public URLs:
/queenstown/
/otago/
or would you still prefer:
/destination/queenstown/
/location/otago/
Would you still prefer those prefixed URLs simply because they reduce the amount of custom routing that has to be maintained?
I'm much more interested in the long-term maintenance trade-offs than in shaving a few characters off the URLs.
If there’s anything important I’ve overlooked with this approach, I’d be interested in hearing about it. Your help would be much appreciated.