Sitecore Parent Item Routing for a Single Page App
More and more often we are asked to host a single page app (SPA) within Sitecore along side regular server generated pages. However, Sitecore is used to being the only show in town and assumes that it is responsible for all parts of a URL path up to the hash. e.g.
/myReactApp (SPA root page loaded by Sitecore)
/myReactApp/step1 (404 page not found)
/myReactApp/step1/itemId1/subItem4 (404 page not found)
Since those pages after /myReactApp don’t exist in Sitecore, the user will see a 404 if they hit that url directly. Instead you can use hash routing. e.g.
/myReactApp (SPA root page loaded by Sitecore, SPA loads its start page)
/myReactApp#step1 (SPA root page loaded by Sitecore, step1 loaded by the SPA)
/myReactApp#step1/itemId1/subItem4 (SPA root page loaded by Sitecore, step1 plus specific items loaded by the SPA)
This will work just fine, but it’s a little bit ugly. Wouldn’t it be nice to just have regular looking URLs so that the SPA is more transparent to the end user? You could do this by creating child pages in Sitecore with names that match the SPA routes, but it would be a nightmare to maintain. A neater option is to add a processor into the item pipeline that will climb up the tree to find the SPA host page that exists in Sitecore. e.g.
/myReactApp (Page is found by Sitecore, SPA start)
/myReactApp/step1 (Sitecore checks for /myReactApp/step1 and can’t find it, so tries /myReactApp, finds that and loads that page. step1 loaded by the SPA)
/myReactApp/step1/itemId1/subItem4 (Sitecore checks for subItem4, then itemId1, then step1, then finds myReactApp and loads that page, step1 plus specific data loaded by the SPA)
How it works
The ParentItemResolver pipeline component loosely follows what the default ItemResolver class does, but walks UP the path to find a match. It…
Checks whether an item has already been found previously in the pipeline, or if the path is /undefined or /itemnotfound. If yes, no need to do anything more and it stops.
Reads the requested path and breaks it up from most specific to least specific, so given the path /myReactApp/step1/itemId1/subItem4, it will generate the list
/myReactApp/step1/itemId1/subItem4
/myReactApp/step1/itemId1
/myReactApp/step1
/myReactApp
Tries to match these paths to an item from most specific to least as shown above.
If it finds an item, it checks to see whether that item has the field MapChildrenToParent, and it’s true. If yes, then this is the item we’re looking for.
The code
\Pipelines\HttpRequest\ParentItemResolver.cs
We’ll going to wire up the processor so that it runs immediately after the ItemResolver. There’s also a setting for the maximum depth that Sitecore should go to, to prevent someone entering a ridiculous path and Sitecore trying to resolve every part of it.
You’ll need to either create a new template that your SPA host page can inherit, or extend an existing template with a checkbox field called MapChildrenToParent.
SEO considerations
When implementing this solution, we were careful to consult with analytics and SEO specialists to understand the impact that this might have on SEO in particular. Search engines don’t like it if you return exactly the same content at different URLs in the same site, and may punish sites that do so in the rankings. In this case, we chose to go ahead with this solution because
Using hash routing would have a similar effect on SEO because search engines don’t consider anything in the URL fragment.
The ability to link to a forward point within the SPA is for users’ and marketing convenience. We may like to link from another page to a particular part of the SPA, but we are not expecting users to come directly from search engines to anywhere other than the beginning of the SPA.
I hope this has been helpful. Thanks for reading. :-)