Proper 404 handling by server with vue-router in history mode - vue.js

When using history mode in vue-router the documentation is suggesting a pretty dodgy way to get around some of the limitations it has.It suggests a server-side configuration that catches all URLs that could be a client-side route and rewriting to root (/) so the client-side app is delivered. And then another catch-all route to a 404 component in the client-side router if no routes match.
Problem is, this will mean your server is returning 200 OK status codes to crawlers/indexers for basically every URL, specifically ones that don’t technically exist.
My thoughts so far:
Use IIS <rewriteMap> to list the valid client-side route patterns I have and use that for matches instead of a catch-all on everything not a file/dir.
Problem: pain to manage in tandem with client-side routes.
Routes defined in server config and handed to client-side router via an api endpoint for registration
Problem: setting up an API when you just want to host a static app is a pain.
Any other suggestions?

Related

app.MapFallbackToFile causes reload the entire SPA site if the URL typed manually

I use the latest recommended SPA + .Net Core-based Web APi pattern where the FE referenced to BE, FE serves proxy to BE during development, and app.UseDefaultFiles()serves index.html where the SPA resides during production. This pattern means no proxy middleware is required as it was in opposite direction when the BE serves FE as a proxy.
app.UseDefaultFiles(); <-- Here the site is loaded first time
app.UseStaticFiles();
app.MapControllers();
app.MapFallbackToFile("/index.html"); <-- Here the site is reloaded if URL typed(changed) manually
Client-side routing is the point. Specifically, I use Vue Router and IIS hosting. When the site is already opened, and a user types URL in the browser, it falls down to app.MapFallbackToFile("/index.html") and then Vue router handles the route.
The problem is that the site is always completely reloading when the URL is just changed (let say from mysite.com/a to mysite.com/b) in this scenario, as I would press F5. It's not always necessarily bad but I would like to control it.
The question is: how to get rid of app.MapFallbackToFile("/index.html") and somehow pass the captured URL to the SPA, as it would be naked SPA without backend which now stays in front of frontend.
If have tried Vue Spa with ASP.NET Core 6 minimal setup and it seems for me, that there is no way to achieve what you want.
When user enters or changes the URL address, the browser navigate away from the page and do a GET request to BE (Backend).
Here is the catch-all fallback route required, otherwise the user gets the 404 error from the web server.
I presume you use the HTML5 History Mode. Here is a part from the Vue Router Docs about this problem.
Since our app is a single page client side app, without a proper
server configuration, the users will get a 404 error if they access
https://example.com/user/id directly in their browser. Now that's
ugly.
Not to worry: To fix the issue, all you need to do is add a simple
catch-all fallback route to your server. If the URL doesn't match any
static assets, it should serve the same index.html page that your app
lives in. Beautiful, again!
If somebody yet knows the solution, please post a new answer.
It would be great to know how to do it!

Single Page Application Routing

Modern single page applications use routing mechanisms which don't have to rely on fragments or additional url parameters, but simply leverage the url path. How does the browser know when to ask the server for a resource and when to ask the single page application for a spa-page controlled by a router? Is there a browser API which makes it possible to take over the control of url handing which is then taken over by e.g. the vue-router or another routing spa library?
In Vue Router (and I assume other libraries/frameworks are the same) this is achieved through the HTML5 history API (pushState(), replaceState(), and popstate) which allows you to manipulate the browser's history but won't cause the browser to reload the page or look for a resource, keeping the UI in sync with the URL.
For example, observe what happens to the address bar when you enter this command in your browser's console
history.pushState({urlPath:'/some/page/on/stackoverflow'},"",'/some/page/on/stackoverflow')
The new URL is even added to your browser's history so if you navigate away from the page and come back to it you'll be directed to the new URL.
Of course all these URLs are non-existent on the server. So to avoid the problem of 404 errors when a user tries to directly access a non-existent resource you'd have to add a fallback route that redirects to your index.html page where your app lives.
Vue Router's HTML5 History Mode
React Router's <BrowserRouter>
How does the browser know when to ask the server for a resource and
when to ask the single page application for a spa-page controlled by a
router?
SPA Frameworks use routing libraries.
Suppose your javascript app is already loaded in the browser. When you navigate to a route that is defined in your routes array, the library prevents an http call to the server and handles it internally in your javascript code. Otherwise the call is forwarded to the server as a GET Http request.
here is an answer that discribes this behaviour with a clear scenario

How to enable offline support when using HTML5 history api

What are the best practices (and how to go about doing it) to support offline mode when using html5 history api for url rewrites?
For example, (hypothetically) I have a PWA SPA application at https://abc.xyz which has internationalization built in. So when I visit this link, the Vue router (which ideally could be any framework - vue, react, angular etc.) redirect me to https://abc.xyz/en.
This works perfectly when I am online (ofcourse, the webserver is also handling this redirect so that app works even if you directly visit the said link).
However, its a different story when I am offline. The service worker caches all resources correctly so when I visit the URL https://abc.xyz everything loads up as expected. However, now if I manually type the URL to https://abc.xyz/en, the app fails to load up.
Any pointers on how to achieve this?
Link to same question in github: https://github.com/vuejs-templates/pwa/issues/188
Yes, this is possible quite trivially with Service Workers. All you have to do is to configure the navigateFallback property of sw-precache properly. It has to point to the cached asset you want the service worker to fetch if it encounters a cache miss.
In the template you posted, you should be good to go if you configure your SWPrecache Webpack Plugin as follows:
new SWPrecacheWebpackPlugin({
...
navigateFallback: '/index.html'
...
})
Again, it is absolutely mandatory that the thing you put inside navigateFallback is cached by the Service Worker already, otherwise this will fail silently.
You can verify if everything was configured correctly by checking two things in your webpack generated service-worker.js:
the precacheConfig Array contains ['/index.html', ...]
in the fetch interceptor of the service worker (at the bottom of the file), the variable navigateFallback is set to the value you configured
If your final App is hosted in a subdirectory, for example when hosting it on Github pages, you also have to configure the stripPrefix and replacePrefix Options correctly.

Using prerender.io with ReactJS for SEO

I've realized that my ReactJS application using react-router does not have any external links. Search engines do not recognize that the site has a lot of linked content. Prerender.io seems like a good solution to this, but I cannot get it to work for my ReactJS application.
Can prerender.io be used with ReactJS? If not, is there another good way to improve SEO for my site without doing all the rendering server-side?
EDIT: Upon further digging, I realized that the issue here is that react-router uses a "#" by default, and not a "#!". Is it possible to make react-router work with a "#!"?
Prerender appears to expect real urls, because otherwise you can't serve the cached html to normal people (the hash isn't sent to the server).
When setting up react-router ensure it's using the history mode:
Router.run(routes, Router.HistoryLocation, function (Handler) {
In your server you'll need to ensure that all routes that don't match a static file are served by either sending index.html, or using prerender.
You can search for 'send all requests to index.html {insert server name here}' to find details on this.
It seems that by default prerender only applies in certain situations. Here's an example if you're using the express.js middleware in node.js:
require('prerender-node').shouldShowPrerenderedPage = function(){ return true }
You should be able to figure out similar modifications for other middleware (it'll require a fork in less flexible languages).

Ember gives 404 after page refresh 404 apache on localhost

This probably has to do with not using rails or hosting in couchapp, but I'd like to solve this problem without involving another layer of code.
I'm writing an Ember app, and when I refresh the browser on any route except the index (home) route I get a 404. Getting to routes only works when it's done through Ember code, such as {{#linkTo}}s or transitionTos.
Apache version: Server version: Apache/2.2.22 (Ubuntu)
This sounds like an issue with one (or all) of your model hooks. Since everything after the # doesn't get sent back to the server, so any link-to or transition wouldn't make any difference,
server/cow is the same to the server as server/cow#/comments/3/posts.
Aka, you might have been passing models down to each nested resource lower using a link-to, but when it reloads that route, the param in the url is passed to the model hook to resolve the model.
As was pointed out below in the comments, if you aren't using the hash tag (aka using location:history or location:none) you need to use some form of url rewrite at the root of your ember application so your url. Be aware that if you choose one of those options you are limiting the functionality of your application to modern browsers (http://caniuse.com/history).