Single Page Applications (SPA) handle the client-side
routing or navigation of pages without the need to send a post back or request
to the server hosting the application. To enable the client-side routing,
applications build using React or Angular and others bundle the application for
a single download of all the page resources. This bundle download allows the
routing service to load the next view container and components when the user
selects a new page, essentially loading a new route, without having to make a
request. Depending on the size of the application, this approach can lead to initial
slow load time on the browser.
To address the initial slow load time, a build optimization
can be done that can enable the loading of the content associated with a route
to be done on demand, which requires a request be sent to the server. The
optimization is handled by using a lazy loading approach, in which the route
content is downloaded as an application chunk from the hosting environment.
This means that instead of downloading the entire app during the initial
download, only an index of URLs pointing to chunk files that are associated to
the route is downloaded. This can also include chunk files for CSS, JS and even
image downloads. As a route is loaded, the index lookup provides the URL of the
chunk to download, thus making the app load faster. When another route is
loaded, a new chunk is downloaded.
Client-Side vs Server-Side Routing
The client-side routing works as designed when the
navigation operations are done normally by the user clicking on menu options or
call-to-action buttons. In some cases, a
user may have to reload or refresh the web application. This action forces the
browser to make a request to the server.
Once the request makes to the server, the server-side routing rules are
applied. If the server routes definitions do not have a handler for all the
routes defined on the client-side, the server fails to find the content that is
requested and responds with an HTTP 404 error, which means that a page is not
found.
By default, the server-side routing knows to always return
the index page hosting the SPA. This is
usually mapped to the root of the domain or / path. However, when a user starts
to navigate the app, the routes change to match a different path. For the
server to be able to understand what to send back when this new path is loaded,
we need to configure the server in a way that it knows to return the index page
for all the routes, not just the root path. For Static Web Apps (SWA) which are
hosted on CDN resources, this is done using a configuration setting for the
application. These settings enable the configuration of the routing and
security policy for the application. Let’s use an example to review that in
more detail.
Static Web Apps Settings
Imagine that we have an SPA app with the following client-site
route configuration:
The above routing information is typical of an app that has
a home, about and contact page. The SPA provides the navigation elements or
call-to-action buttons, so the user can select any of those pages. When the user is on the home page which is defined
by the home route or /, a page reload does not cause any problem as this path
is usually associated to the index page of the application on the server
routing configuration.
When the user navigates to the other route locations, for
example /about, a page reloads sends a post back to the server, and if there is
no handling for that page the 404 error is created. Depending on the hosting resource,
the 404 page can be system generated, which takes the user away from the
application experience altogether.
To manage this concern, the latest release of SWA provides
the staticwepapps.config.json file. This file is required to be able to
configure the server-side route and security information for the app. It is also
used to override the behavior of some HTTP operations, as well as
configuring HTTP responses. For the scope of this conversation, we focus on the
routing configuration only.
Note: At the time of this writing, the routes.json file
has been deprecated for the staticwebapps.config.json. The configuration
between these files has some differences, so carefully review the options being
used, just renaming routes.json will lead to problems on the application
behavior.
Routing and Response Overrides
The routes' configuration enables us to add server-side
routing rules with security, for authentication and authorization requirements,
as well redirect rules for a request. To
avoid the 404 error, the SWA configuration should have an entry for every client-side
route configuration. For our specific example, this means that we should add the
routes for the /about and /contact-us route. This is done by adding route entries
in the routes' collection, as shown below:
"routes": [
{
"route": "/",
"allowedRoles": ["anonymous"] }, {
"route": "/about",
"allowedRoles": ["anonymous"] }, {
"route": "/contact-us",
"allowedRoles": ["anonymous"]
} ], |
The routes on the server-side configuration lists all the
client-side configuration and add a security role to enable anonymous users to access
the route.
Do We Need to Map All the Routes?
We do not only if we use a fallback policy. Our routing
configuration only has three separate routes, and thus it is simple to manage.
This however is not reflective of a complex app which can have several route
entries. In addition, having to add every single client-side entry on the
server is error-prone, as a route can be configured improperly or just forgotten
to be included.
The Static Web App team also figure as much, so a fallback
setting was introduced on more recent releases. This setting allows the server
configuration to “fallback” to the default route when a route is not defined. This
works just as good because for SPA, we always want the index page to be sent to
the client. As the page is loaded on the browsers, the SPA routing service
identifies the route information and download the chunk files associated to the
route. This fallback setting looks as follows:
"navigationFallback": {
"rewrite": "index.html",
"exclude": ["/images/*.{png,jpg,gif}", "/css/*"]
}, |
On the fallback setting, we should note that we are doing a
rewrite operation instead of redirect. This is efficient because a rewrite is handled
on the server, so there is no client-side trip and another request as the
redirect operation creates. We should
also notice that we do not want to rewrite missing resources to the index page.
To avoid this, we exclude all the images and CSS files. The order on how these
settings is configured on the file is also relevant. Usually, the settings that
come later on the document take precedence and override the previous settings.
To help on this, we can look at a complete staticwepapps.config.json.
Are There Client-Side Page Not Found Errors?
Yes, there are these errors as well. These errors are different
from an HTTP 404 error. These client-side errors indicate that there is some call-to-action
element on the app that has a path with no routing configuration. This is not a
server error, so there should also be a fallback to handle that problem on the
client routing configuration. This is often done by creating a wildcard route
entry as the last route step, so when a route is not found, it can load the page
not found component.
Conclusion
SPA application routing is done on the client-side of the application, but when the user does an operation that can cause the SPA to bootstrap again, a server-side request is made. If the hosting environment web server, CDN do not have routing configuration or a fallback to handle new routes added to the client side, the hosting environment returns a 404-error page that takes the user completely out of the application. Therefore, it is important to add routing to both the client and server and manage route issues on both ends. This should help us guards against 404 page not found errors.
Have you experienced similar problems with your apps?
Send question or comment at Twitter @ozkary