When a single-page app returns “Cannot GET”
Single-page apps often rely on the History API and click handlers to swap content without a fresh page load. That approach lets the URL change as the user moves between views while keeping the back button working. But a common flaw hides just beneath the surface: refresh the page or open that URL in a new tab and you may hit an unexpected 404 page with a Cannot GET /route message.
That “sneaky 404” occurs because the server was only configured to serve your app at the root URL. The front-end JavaScript navigates to other paths via the History API, but when the browser makes an actual request for one of those paths, the server has no matching route and responds with a 404 status.
This is not just a cosmetic issue. Search engines will not index those URLs if clicking a result leads users to an error page instead of the intended content.
Why the app works until it doesn’t
In a typical setup, clicking a link inside the app never sends a full request to the server — the JavaScript intercepts the navigation and updates the URL. Everything appears fine until a reload or a direct visit triggers that server request, exposing the missing route.
The server-side fix
The project in question uses an Express.js server. In server.js, the initial route setup only serves index.html for requests to /. To support all the URLs the app can generate, that route needs to be broadened.
Rather than listing each possible path, change the route so /* matches any URL and always responds with the app:
app.get('/*', function(request, response) {
response.sendFile(__dirname + '/views/index.html');
});
The /* wildcard ensures that any request, current or future, receives the single-page app from index.html, letting the client-side code handle the rest. After this change, refreshing the page or opening links in an incognito window works as expected.



