Not able to implement strict Content Security Policy with Google maps APIs - google-maps

I am getting below error multiple times in the console of developer tool of chrome for common.js file of maps.googleapis.com-
common.js:15
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' https://fonts.googleapis.com https://s3.amazonaws.com https://maxcdn.bootstrapcdn.com". Either the 'unsafe-inline' keyword, a hash ('sha256-mmA4m52ZWPKWAzDvKQbF7Qhx9VHCZ2pcEdC0f9Xn/Po='), or a nonce ('nonce-...') is required to enable inline execution.
I need to use strict CSP policy so can't use unsafe-inline or unsafe-eval to relax the policy.
To support strict CSP policy, inline styling and scripting are not allowed. And it seems inline stylings have been used in the common.js of the google map api due to which I am getting the above error.
Any suggestion?

Any suggestion?
Use 'nonce-value' token in script-src and the same one in style-src. If you call the GMaps API with nonce='value' attribute:
<script async defer src='//maps.googleapis.com/maps/api/js?key=<api_key_here>&callback=initMap' nonce='base64value'></script>
The Google maps API script redistributes this nonce='base64value' into all child external scripts and inline styles blocks. You can check it in the demo of 'nonce' with Google map, just select 'nonce' checkbox.
Edit 24-07-2021:
I can confirm that:
GMap made some changes and does not redistribute nonce from script tag into styles.
A workaround by Max Visser no longer works as of now.
Therefore, unfortunately the answer is: Use 'unsafe-inline' and wait for Google to implement 'nonce' for styles.

Currently google maps requires you to have unsafe-inline in your CSP for style-src. For script-src, it still works.~~
The test of CSPlite.com granty mentioned has been adjusted; As of writing this answer the test says
At the end of April 2021, the Google maps script stopped passing the 'nonce-value' from the parent script to the blocks of child styles <style>...</style>
The temporary solution I found is to add all the styles from Google Maps to our third-party CSS code. This way you still get the error you have in console but the visual bugs resulting from style-src blocking inline styles from Google maps will go away. Method we used for this is to just copy over all the inline styles added by google maps.
If you find this answer and also want Google Maps to support distribution of nonce values to their inline style blocks again, please leave a comment here.
Edit 22-06-2021:
GMap does not officially support nonces yet. Recently a change went in that improved Maps JS's ability to handle separate nonces for scripts and styles. Now, if a site has no nonce on a <style> or <link rel="stylesheet">, this results in no nonce being applied to Maps JS stylesheets.
As a workaround, your site can include an empty <style> with the nonce provided, and GMaps JS will pick it up.

For me the solution was here: https://csplite.com/csp/test42/#styles_nonce_workaround
Adding <style nonce='base64value'></style> did the job.

Related

Using Imported Fonts in a Google App

I am creating a web app using Google Apps Script. When I wanted to import a photo from Google Docs, I followed the advice from this question and it worked perfectly. I thought a similar process would work for using a custom font hosted on Docs, but it throws a CORS exception.
The relevant code:
#font-face {
font-family: MyCustomFont;
src: url("https://drive.google.com/uc?export=download&id=1r2a5nd6om75url39428dfju4");
}
#headBanner {
font-family: MyCustomFont;
}
<div id="headBanner">
<p>This text should be in MyCustomFont</p>
</div>
The error it throws is: "Access to Font at '[URL in font]' from origin '[Google Script page I'm using]' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '[Google Script page I'm using]' is therefore not allowed access."
How could I fix this? I just find it perplexing that it would throw a CORS exception for the font file but not for the picture.
Font files, along with AJAX requests using fetch are the only things that use CORS. Image requests and static inline requests for JavaScript (via tags) and CSS do not invoke CORS.
In this case, there is nothing you can do, short of (as #andrei-gheorghiu suggests) downloading the font file manually, saving it to your server and then accessing it from there. Whilst you can do that technically, it would technically be stealing. Hence his use of "Can, not may"...
Fun fact - CORS was basically invented for the specific use-case you've come across - so that 'font factories' could design fonts (which cost them money, obviously) and then be able to allow/deny access to those font files, depending on whether you have paid a license fee.
No doubt not the answer you wanted, but the answer nevertheless.

Content Security Policy with Dynamic Button

I'm implementing Content Security Policy (CSP) on a site. Below is the CSP policy that I'm testing.
Content-Security-Policy: script-src 'self' 'nonce-random' 'strict-dynamic';
The site uses a third party js script library. The script library injects dynamic content on a page. The dynamic content has inline event handlers. Below is a simple HTML page with a script that mimics the site + the third party library's behavior.
<!DOCTYPE html>
<html>
<head>
<title>CSP Test Page</title>
<script nonce="random">
document.addEventListener('DOMContentLoaded', function (event) {
var el = document.createElement('button');
el.innerHTML = 'Click Me';
el.setAttribute('onclick', "doSomething()");
document.body.appendChild(el);
});
function doSomething() {
alert('I did something.');
}
</script>
</head>
<body>
</body>
</html>
The inline event handlers on the dynamically added button triggers the following error message in the Chrome console:
Refused to execute inline event handler because it violates the
following Content Security Policy directive: "script-src 'self'
'nonce-random' 'strict-dynamic'". Either the 'unsafe-inline' keyword,
a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable
inline execution.
Any suggestions on how to address this issue from a CSP standpoint? I can't change the code of the third party library that is adding the dynamically generated content with the inline event handler.
CSP blocks all inline event handlers, including code added by the third-party library, so unfortunately there is no simple way to solve this without refactoring the CSP-incompatible dependency.
In the long term, CSP3 might provide the ability to whitelist trusted scripts inside event handlers via the 'unsafe-hashed-attributes' feature, but this isn't yet shipping in any stable browser.
In the meantime, one possible workaround is manually removing the attribute with the inline event handler after invoking the external library. That is, you could do something like:
var el = document.createElement('button');
library.doStuff(el);
// Now that the library has run we will change the DOM to be compatible with CSP.
el.onclick = function() { doSomething() };
el.removeAttribute('onclick');
Note that assigning a function to the onclick property directly is okay when it comes to CSP, as opposed to setting the onclick attribute on the HTML element, which is blocked by CSP because it converts a string to code. This will work and avoid CSP violations, but it only makes sense if there is a small number of instances of inline event handlers in the library; otherwise this could get quite tedious.
As a side note, your CSP might benefit from fallbacks for older browsers which don't support 'strict-dynamic', similar to this example.

How to CSP restrict to file:/// urls in chrome?

I have an iframe in a webview which loads a script from the android app's asset using the following :-
<script src='file:///android_asset/trusted-iframe-script.js'></script>
Now I want to have a content security policy on the iframe such that no other script can be loaded.
For this I added the following to the iframe CSP
script-src: 'file:///android_asset/trusted-iframe-script.js';
This doesn't work since the file uri is ignored by chrome.
The source list for Content Security Policy directive 'script-src' contains an invalid source: 'file:///android_asset/trusted-iframe-script.js'. It will be ignored.
Refused to load the script 'file:///android_asset/trusted-iframe-script.js' because it violates the following Content Security Policy directive: "script-src file:///android_asset/trusted-iframe-script.js".
I read about filesystem uri, but that requires requesting access to the user but I actually only need access to my own assets and not filesystem in general. I also read about blob: urls but that feels akin to inlining the whole script
What is the right way to csp restrict to only file urls ?
CSP URI's don't have quote marks - so try script-src: file:///android_asset/trusted-iframe-script.js;
If that doesn't work most Android browsers now support CSP2, which lets you specify a hash for a supported script.
If the CSP is set on the "page" which contains the iframe you should use
the ​child-src: directive instead of script-src: (Source)
Then, i am not use how to include an assert file and you could try with:
​child-src: file:///android_asset/trusted-iframe-script.js
// i cannot test it
​child-src: filesystem:///android_asset/trusted-iframe-script.js
// source and check if still requires requesting access to the user
child-src: https://your.trusted.website.com/trusted-iframe-script.js
// this should work but it requires to have a trusted server and the app must connect to the web (no obvious condition)
In short, this cannot be done for a sandboxed iframe.
Chrome CSP doesn't allow file urls to be whitelisted as a script src. You could use the directive file: (without any url) and that would work if the iframe wasn't sandboxed. But this is a bad idea, since
A. my iframe is sandboxed, and
B. this is an undocumented keyword which may stop working at anytime.
I also tried creating a blob url for the content and instead passing that to the iframe but that too doesn't work unless you set allow-same-origin on the iframe sandbox attribute.

How to properly load google map api in JSFiddle ?

I already registered my project with Google to get an API_KEY.
I already enable the Google MAP API in the API console. See here
I already set the referred to my JSFiddle Account.
[ http://jsfiddle.net/bheng/* ]
Here the link LINK
I am not sure why, it didn't load the map while it should.
What am I doing wrong ?
You are not including the Google Maps Javascript API. Note that you don't need a key (particularly for jsfiddle). Add this to the "External Resources":
https://maps.googleapis.com/maps/api/js
And add JQuery under "Frameworks & Extensions":
http://jsfiddle.net/9wc30ndf/2/
But you need to read the message also (you are missing the "q=")
http://jsfiddle.net/9wc30ndf/3/
You need to use *fiddle.jshell.net/bheng/* as the referrer expression for your browser-API-key since jsfiddle loads the JS sources from this domain (see referer request-header).

How do search engines deal with AngularJS applications?

I see two issues with AngularJS application regarding search engines and SEO:
1) What happens with custom tags? Do search engines ignore the whole content within those tags? i.e. suppose I have
<custom>
<h1>Hey, this title is important</h1>
</custom>
would <h1> be indexed despite being inside custom tags?
2) Is there a way to avoid search engines of indexing {{}} binds literally? i.e.
<h2>{{title}}</h2>
I know I could do something like
<h2 ng-bind="title"></h2>
but what if I want to actually let the crawler "see" the title? Is server-side rendering the only solution?
(2022) Use Server Side Rendering if possible, and generate URLs with Pushstate
Google can and will run JavaScript now so it is very possible to build a site using only JavaScript provided you create a sensible URL structure. However, pagespeed has become a progressively more important ranking factor and typically pages built clientside perform poorly on initial render.
Serverside rendering (SSR) can help by allowing your pages to be pre-generated on the server. Your html containst the div that will be used as the page root, but this is not an empty div, it contains the html that the JavaScript would have generated if it were allowed to run.
The client downloads the HTML and renders it giving a very fast initial load, then it executes the JavaScript replacing the content of the root div with generated content in a process known as hydration.
Many newer frameworks come with SSR built in, notably NextJS.
(2015) Use PushState and Precomposition
The current (2015) way to do this is using the JavaScript pushState method.
PushState changes the URL in the top browser bar without reloading the page. Say you have a page containing tabs. The tabs hide and show content, and the content is inserted dynamically, either using AJAX or by simply setting display:none and display:block to hide and show the correct tab content.
When the tabs are clicked, use pushState to update the URL in the address bar. When the page is rendered, use the value in the address bar to determine which tab to show. Angular routing will do this for you automatically.
Precomposition
There are two ways to hit a PushState Single Page App (SPA)
Via PushState, where the user clicks a PushState link and the content is AJAXed in.
By hitting the URL directly.
The initial hit on the site will involve hitting the URL directly. Subsequent hits will simply AJAX in content as the PushState updates the URL.
Crawlers harvest links from a page then add them to a queue for later processing. This means that for a crawler, every hit on the server is a direct hit, they don't navigate via Pushstate.
Precomposition bundles the initial payload into the first response from the server, possibly as a JSON object. This allows the Search Engine to render the page without executing the AJAX call.
There is some evidence to suggest that Google might not execute AJAX requests. More on this here:
https://web.archive.org/web/20160318211223/http://www.analog-ni.co/precomposing-a-spa-may-become-the-holy-grail-to-seo
Search Engines can read and execute JavaScript
Google has been able to parse JavaScript for some time now, it's why they originally developed Chrome, to act as a full featured headless browser for the Google spider. If a link has a valid href attribute, the new URL can be indexed. There's nothing more to do.
If clicking a link in addition triggers a pushState call, the site can be navigated by the user via PushState.
Search Engine Support for PushState URLs
PushState is currently supported by Google and Bing.
Google
Here's Matt Cutts responding to Paul Irish's question about PushState for SEO:
http://youtu.be/yiAF9VdvRPw
Here is Google announcing full JavaScript support for the spider:
http://googlewebmastercentral.blogspot.de/2014/05/understanding-web-pages-better.html
The upshot is that Google supports PushState and will index PushState URLs.
See also Google webmaster tools' fetch as Googlebot. You will see your JavaScript (including Angular) is executed.
Bing
Here is Bing's announcement of support for pretty PushState URLs dated March 2013:
http://blogs.bing.com/webmaster/2013/03/21/search-engine-optimization-best-practices-for-ajax-urls/
Don't use HashBangs #!
Hashbang URLs were an ugly stopgap requiring the developer to provide a pre-rendered version of the site at a special location. They still work, but you don't need to use them.
Hashbang URLs look like this:
domain.example/#!path/to/resource
This would be paired with a metatag like this:
<meta name="fragment" content="!">
Google will not index them in this form, but will instead pull a static version of the site from the escaped_fragments URL and index that.
Pushstate URLs look like any ordinary URL:
domain.example/path/to/resource
The difference is that Angular handles them for you by intercepting the change to document.location dealing with it in JavaScript.
If you want to use PushState URLs (and you probably do) take out all the old hash style URLs and metatags and simply enable HTML5 mode in your config block.
Testing your site
Google Webmaster tools now contains a tool which will allow you to fetch a URL as Google, and render JavaScript as Google renders it.
https://www.google.com/webmasters/tools/googlebot-fetch
Generating PushState URLs in Angular
To generate real URLs in Angular, rather than # prefixed ones, set HTML5 mode on your $locationProvider object.
$locationProvider.html5Mode(true);
Server Side
Since you are using real URLs, you will need to ensure the same template (plus some precomposed content) gets shipped by your server for all valid URLs. How you do this will vary depending on your server architecture.
Sitemap
Your app may use unusual forms of navigation, for example hover or scroll. To ensure Google is able to drive your app, I would probably suggest creating a sitemap, a simple list of all the URLs your app responds to. You can place this at the default location (/sitemap or /sitemap.xml), or tell Google about it using webmaster tools.
It's a good idea to have a sitemap anyway.
Browser support
Pushstate works in IE10. In older browsers, Angular will automatically fall back to hash style URLs
A demo page
The following content is rendered using a pushstate URL with precomposition:
http://html5.gingerhost.com/london
As can be verified, at this link, the content is indexed and is appearing in Google.
Serving 404 and 301 Header status codes
Because the search engine will always hit your server for every request, you can serve header status codes from your server and expect Google to see them.
Update May 2014
Google crawlers now executes javascript - you can use the Google Webmaster Tools to better understand how your sites are rendered by Google.
Original answer
If you want to optimize your app for search engines there is unfortunately no way around serving a pre-rendered version to the crawler. You can read more about Google's recommendations for ajax and javascript-heavy sites here.
If this is an option I'd recommend reading this article about how to do SEO for Angular with server-side rendering.
I’m not sure what the crawler does when it encounters custom tags.
Let's get definitive about AngularJS and SEO
Google, Yahoo, Bing, and other search engines crawl the web in traditional ways using traditional crawlers. They run robots that crawl the HTML on web pages, collecting information along the way. They keep interesting words and look for other links to other pages (these links, the amount of them and the number of them come into play with SEO).
So why don't search engines deal with javascript sites?
The answer has to do with the fact that the search engine robots work through headless browsers and they most often do not have a javascript rendering engine to render the javascript of a page. This works for most pages as most static pages don't care about JavaScript rendering their page, as their content is already available.
What can be done about it?
Luckily, crawlers of the larger sites have started to implement a mechanism that allows us to make our JavaScript sites crawlable, but it requires us to implement a change to our site.
If we change our hashPrefix to be #! instead of simply #, then modern search engines will change the request to use _escaped_fragment_ instead of #!. (With HTML5 mode, i.e. where we have links without the hash prefix, we can implement this same feature by looking at the User Agent header in our backend).
That is to say, instead of a request from a normal browser that looks like:
http://www.ng-newsletter.com/#!/signup/page
A search engine will search the page with:
http://www.ng-newsletter.com/?_escaped_fragment_=/signup/page
We can set the hash prefix of our Angular apps using a built-in method from ngRoute:
angular.module('myApp', [])
.config(['$location', function($location) {
$location.hashPrefix('!');
}]);
And, if we're using html5Mode, we will need to implement this using the meta tag:
<meta name="fragment" content="!">
Reminder, we can set the html5Mode() with the $location service:
angular.module('myApp', [])
.config(['$location',
function($location) {
$location.html5Mode(true);
}]);
Handling the search engine
We have a lot of opportunities to determine how we'll deal with actually delivering content to search engines as static HTML. We can host a backend ourselves, we can use a service to host a back-end for us, we can use a proxy to deliver the content, etc. Let's look at a few options:
Self-hosted
We can write a service to handle dealing with crawling our own site using a headless browser, like phantomjs or zombiejs, taking a snapshot of the page with rendered data and storing it as HTML. Whenever we see the query string ?_escaped_fragment_ in a search request, we can deliver the static HTML snapshot we took of the page instead of the pre-rendered page through only JS. This requires us to have a backend that delivers our pages with conditional logic in the middle. We can use something like prerender.io's backend as a starting point to run this ourselves. Of course, we still need to handle the proxying and the snippet handling, but it's a good start.
With a paid service
The easiest and the fastest way to get content into search engine is to use a service Brombone, seo.js, seo4ajax, and prerender.io are good examples of these that will host the above content rendering for you. This is a good option for the times when we don't want to deal with running a server/proxy. Also, it's usually super quick.
For more information about Angular and SEO, we wrote an extensive tutorial on it at http://www.ng-newsletter.com/posts/serious-angular-seo.html and we detailed it even more in our book ng-book: The Complete Book on AngularJS. Check it out at ng-book.com.
You should really check out the tutorial on building an SEO-friendly AngularJS site on the year of moo blog. He walks you through all the steps outlined on Angular's documentation. http://www.yearofmoo.com/2012/11/angularjs-and-seo.html
Using this technique, the search engine sees the expanded HTML instead of the custom tags.
This has drastically changed.
http://searchengineland.com/bing-offers-recommendations-for-seo-friendly-ajax-suggests-html5-pushstate-152946
If you use:
$locationProvider.html5Mode(true);
you are set.
No more rendering pages.
Things have changed quite a bit since this question was asked. There are now options to let Google index your AngularJS site. The easiest option I found was to use http://prerender.io free service that will generate the crwalable pages for you and serve that to the search engines. It is supported on almost all server side web platforms. I have recently started using them and the support is excellent too.
I do not have any affiliation with them, this is coming from a happy user.
Angular's own website serves simplified content to search engines: http://docs.angularjs.org/?_escaped_fragment_=/tutorial/step_09
Say your Angular app is consuming a Node.js/Express-driven JSON api, like /api/path/to/resource. Perhaps you could redirect any requests with ?_escaped_fragment_ to /api/path/to/resource.html, and use content negotiation to render an HTML template of the content, rather than return the JSON data.
The only thing is, your Angular routes would need to match 1:1 with your REST API.
EDIT: I'm realizing that this has the potential to really muddy up your REST api and I don't recommend doing it outside of very simple use-cases where it might be a natural fit.
Instead, you can use an entirely different set of routes and controllers for your robot-friendly content. But then you're duplicating all of your AngularJS routes and controllers in Node/Express.
I've settled on generating snapshots with a headless browser, even though I feel that's a little less-than-ideal.
A good practice can be found here:
http://scotch.io/tutorials/javascript/angularjs-seo-with-prerender-io?_escaped_fragment_=tag
As of now Google has changed their AJAX crawling proposal.
Times have changed. Today, as long as you're not blocking Googlebot from crawling your JavaScript or CSS files, we are generally able to render and understand your web pages like modern browsers.
tl;dr: [Google] are no longer recommending the AJAX crawling proposal [Google] made back in 2009.
Google's Crawlable Ajax Spec, as referenced in the other answers here, is basically the answer.
If you're interested in how other search engines and social bots deal with the same issues I wrote up the state of art here: http://blog.ajaxsnapshots.com/2013/11/googles-crawlable-ajax-specification.html
I work for a https://ajaxsnapshots.com, a company that implements the Crawlable Ajax Spec as a service - the information in that report is based on observations from our logs.
I have found an elegant solution that would cover most of your bases. I wrote about it initially here and answered another similar Stack Overflow question here which references it.
FYI this solution also includes hard coded fallback tags in case JavaScript isn't picked up by the crawler. I haven't explicitly outlined it, but it is worth mentioning that you should be activating HTML5 mode for proper URL support.
Also note: these aren't the complete files, just the important parts of those that are relevant. I can't help with writing the boilerplate for directives, services, etc.
app.example
This is where you provide the custom metadata for each of your routes (title, description, etc.)
$routeProvider
.when('/', {
templateUrl: 'views/homepage.html',
controller: 'HomepageCtrl',
metadata: {
title: 'The Base Page Title',
description: 'The Base Page Description' }
})
.when('/about', {
templateUrl: 'views/about.html',
controller: 'AboutCtrl',
metadata: {
title: 'The About Page Title',
description: 'The About Page Description' }
})
metadata-service.js (service)
Sets the custom metadata options or use defaults as fallbacks.
var self = this;
// Set custom options or use provided fallback (default) options
self.loadMetadata = function(metadata) {
self.title = document.title = metadata.title || 'Fallback Title';
self.description = metadata.description || 'Fallback Description';
self.url = metadata.url || $location.absUrl();
self.image = metadata.image || 'fallbackimage.jpg';
self.ogpType = metadata.ogpType || 'website';
self.twitterCard = metadata.twitterCard || 'summary_large_image';
self.twitterSite = metadata.twitterSite || '#fallback_handle';
};
// Route change handler, sets the route's defined metadata
$rootScope.$on('$routeChangeSuccess', function (event, newRoute) {
self.loadMetadata(newRoute.metadata);
});
metaproperty.js (directive)
Packages the metadata service results for the view.
return {
restrict: 'A',
scope: {
metaproperty: '#'
},
link: function postLink(scope, element, attrs) {
scope.default = element.attr('content');
scope.metadata = metadataService;
// Watch for metadata changes and set content
scope.$watch('metadata', function (newVal, oldVal) {
setContent(newVal);
}, true);
// Set the content attribute with new metadataService value or back to the default
function setContent(metadata) {
var content = metadata[scope.metaproperty] || scope.default;
element.attr('content', content);
}
setContent(scope.metadata);
}
};
index.html
Complete with the hardcoded fallback tags mentioned earlier, for crawlers that can't pick up any JavaScript.
<head>
<title>Fallback Title</title>
<meta name="description" metaproperty="description" content="Fallback Description">
<!-- Open Graph Protocol Tags -->
<meta property="og:url" content="fallbackurl.example" metaproperty="url">
<meta property="og:title" content="Fallback Title" metaproperty="title">
<meta property="og:description" content="Fallback Description" metaproperty="description">
<meta property="og:type" content="website" metaproperty="ogpType">
<meta property="og:image" content="fallbackimage.jpg" metaproperty="image">
<!-- Twitter Card Tags -->
<meta name="twitter:card" content="summary_large_image" metaproperty="twitterCard">
<meta name="twitter:title" content="Fallback Title" metaproperty="title">
<meta name="twitter:description" content="Fallback Description" metaproperty="description">
<meta name="twitter:site" content="#fallback_handle" metaproperty="twitterSite">
<meta name="twitter:image:src" content="fallbackimage.jpg" metaproperty="image">
</head>
This should help dramatically with most search engine use cases. If you want fully dynamic rendering for social network crawlers (which are iffy on JavaScript support), you'll still have to use one of the pre-rendering services mentioned in some of the other answers.
With Angular Universal, you can generate landing pages for the app that look like the complete app and then load your Angular app behind it.
Angular Universal generates pure HTML means no-javascript pages in server-side and serve them to users without delaying. So you can deal with any crawler, bot and user (who already have low cpu and network speed).Then you can redirect them by links/buttons to your actual angular app that already loaded behind it. This solution is recommended by official site. -More info about SEO and Angular Universal-
Use something like PreRender, it makes static pages of your site so search engines can index it.
Here you can find out for what platforms it is available: https://prerender.io/documentation/install-middleware#asp-net
Crawlers (or bots) are designed to crawl HTML content of web pages but due to AJAX operations for asynchronous data fetching, this became a problem as it takes sometime to render page and show dynamic content on it. Similarly, AngularJS also use asynchronous model, which creates problem for Google crawlers.
Some developers create basic html pages with real data and serve these pages from server side at the time of crawling. We can render same pages with PhantomJS on serve side which has _escaped_fragment_ (Because Google looks for #! in our site urls and then takes everything after the #! and adds it in _escaped_fragment_ query parameter). For more detail please read this blog .
The crawlers do not need a rich featured pretty styled gui, they only want to see the content, so you do not need to give them a snapshot of a page that has been built for humans.
My solution: to give the crawler what the crawler wants:
You must think of what do the crawler want, and give him only that.
TIP don't mess with the back. Just add a little server-sided frontview using the same API