Does the "Sign In with Google" button require CSP style-src 'unsafe-inline'? - content-security-policy

I added a "Sign In with Google" button to my test web app.
I tried to add a CSP following the advise at Setup instructions > Content Security Policy:
script-src https://accounts.google.com/gsi/client;frame-src https://accounts.google.com/gsi/;connect-src https://accounts.google.com/gsi/;style-src https://accounts.google.com/gsi/style;report-uri https://localhost.rubenlaguna.com/csp-report;default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src-attr 'none';upgrade-insecure-requests
The button displays ok, but I see a CSP violation for style-src-elem reported:
csp-report {
'csp-report': {
'document-uri': 'https://localhost.rubenlaguna.com/',
referrer: '',
'violated-directive': 'style-src-elem',
'effective-directive': 'style-src-elem',
'original-policy': "script-src https://accounts.google.com/gsi/client;frame-src https://accounts.google.com/gsi/;connect-src https://accounts.google.com/gsi/;style-src https://accounts.google.com/gsi/style;report-uri https://localhost.rubenlaguna.com/csp-report;default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src-attr 'none';upgrade-insecure-requests",
disposition: 'enforce',
'blocked-uri': 'inline',
'line-number': 383,
'column-number': 8676,
'source-file': 'https://accounts.google.com/gsi/client',
'status-code': 200,
'script-sample': ''
}
}
I can get rid of the violation by adding 'unsafe-inline' to the style-src. But I was wondering if it actually required or not, or if there is a way to get rid of this violation by changing the CSP?

It's possible to avoid the unsafe-inline completely by using nonce (See CSP nonce, style-src nonce)
The CSP violation happens because the google client library (https://accounts.google.com/gsi/client) creates some inline styles. Fortunately, the client library will "pass-through" the document.currentScript.nonce to the inline style. If the inline style has a nonce then you can suppress the CSP violation by allowing 'nonce-xxxx'
First, you need to pass a nonce value to the google client library like this
<script nonce="xxx123" src="https://accounts.google.com/gsi/client" async defer></script>
the nonce should be random value that changes in every reload (see examples below on how to achieve this with express.js)
Then, you add the following in your CSP
style-src https://accounts.google.com/gsi/ 'nonce-xxx123';
If you are using express.js to generate your pages you could have a middleware like this to generate the nonce, store it in the request and set the Content-Security-Policy header:
app.use((req, res, next) => { // express middleware
req.nonce = crypto.randomBytes(16).toString("base64url"); // we'll use when rendering the HTML
const csp = [
`script-src https://accounts.google.com/gsi/client;`,
`frame-src https://accounts.google.com/gsi/;`,
`connect-src https://accounts.google.com/gsi/;`,
`style-src https://accounts.google.com/gsi/style 'nonce-${req.nonce}';`, // NOTE: allow the nonce
`report-uri ${process.env.SITE_ADDRESS}/csp-report;`,
"default-src 'self';",
"base-uri 'self';",
"font-src 'self' https: data:;",
"form-action 'self';",
"frame-ancestors 'self';",
"img-src 'self' data:;",
"object-src 'none';",
"script-src-attr 'none';",
"upgrade-insecure-requests",
];
const csp1 = csp.join("");
res.header("Content-Security-Policy", csp1);
next();
});
Then the req.nonce can be used to render the page (using EJS templates):
app.get("/", (req, res, next) => {
res.render("login", {
google_client_id: process.env.GOOGLE_CLIENT_ID,
site_address: process.env.SITE_ADDRESS,
nonce: req.nonce, //NOTE: the EJS template needs this value
});
});
that will render the template at views/index.ejs using <script nonce="<%=nonce%>" ...:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HTML 5 Boilerplate</title>
<script nonce="<%=nonce%>" src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div>
<div
id="g_id_onload"
data-client_id="<%=google_client_id%>"
data-context="signin"
data-ux_mode="redirect"
data-login_uri="<%=site_address%>/sign-in-with-google-callback"
data-itp_support="true"
></div>
<div
class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="left"
></div>
</div>
</body>
</html>

Related

Having difficulty getting the nonce workaround to work for trix-editor style-src content-security-policy violations

I am using the trix-editor (v1.3.1) in a node.js project. The module's trix.js includes some style elements that violate inline Content-Security-Policy rules. It seems that a workaround was implemented in v1.2.2, which requires that a nonce be included in a meta tag in the page where the trix editor is used, like so:
<meta name="csp-nonce" content="nonce-goes-here">
or
<meta name="trix-style-nonce" content="nonce-goes-here">
after which trix is expected to automatically pick up the nonce and use it in the style elements that generate the CSP violation.
I am using helmet to implement CSP, and create and use the nonce as suggested by the helmet instructions on npm, which is like this:
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
next();
});
app.use(
helmet.contentSecurityPolicy({
useDefaults: true,
directives: {
styleSrc: ["'self'", "fonts.googleapis.com", (req, res) => `'nonce-${res.locals.cspNonce}'`],
},
})
);
Then, in the view I provide it using handlebars, like so:
<link href="/assets/vendor/trix/trix.css" rel="stylesheet" >
<meta name="trix-style-nonce" content="{{ cspNonce }}">
<meta name="csp-nonce" content="{{ cspNonce }}">
...
<script src='/assets/vendor/trix/trix.js'></script>
I can print out the nonce to the console and see that it is correctly provided anew to the page on each load. However, helmet continues to report CSP violations from the same trix style sources, so the nonce is not being attached to those style elements, it would seem. Any ideas what I am missing here?
My silly mistake: I had the meta element in the wrong place. In the meantime, I discovered that the nonce with meta name 'trix-style-nonce' doesn't work, while it does work with the meta name 'csp-nonce'.

How to share constants across content and popup?

I have the following setup. When I include an export statement in global_constants.jsI get an unexpected token export in content.js.
In popup.js I get an error for unexpected { for the line with my import statement.
And I've tried a bunch of other combinations and I can't seem to get my constants file to be accepted by both popup and content.
global_constants.js
const URL_REGEX = new RegExp("foobar");
popup/popup.html
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="popup.css">
<script type="module" src="../global_constants.js"></script>
<script src="popup.js"></script>
</head>
popup/popup.js
import {URL_REGEX} from '../global_constants';
mainfest.json
"content_scripts": [
{
"js": [
"jquery-3.2.1.min.js", "global_constants.js", "content.js"

Loading a js file not in the public folder in a Twig template

My file structure looks like this:
---public(Documentroot folder, where all my css,js, images loads from)
---models
---modules {all my modules customer, db_management e.t.c)
---scripts
|---ivr_builder
|------ui-bootstrap-tpls-0.2.js
--twig_templates {All my ta}
|---layout.twig
In my index.php file, I have this:
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../vendor/autoload.php';
spl_autoload_register(function ($classname) {
require "../models/" . $classname . ".php";
});
$config['displayErrorDetails'] = false;
$app = new \Slim\App(["settings" => $config]);
$container = $app->getContainer();
// Register component on container
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig(
'/var/www/html/ivr/twig_templates', ['cache' => false]);
$env = $view->getEnvironment();
$lexer = new Twig_Lexer($env, array(
'tag_comment' => array('{#', '#}'),
'tag_block' => array('{%', '%}'),
'tag_variable' => array('[[', ']]'),
'interpolation' => array('#{', '}'),
));
$env->setLexer($lexer);
$loader = new \ Twig_Loader_Filesystem('ivr/scripts/ivr_builder/ui-bootstrap-tpls-0.13.4.js');
$env->getLoader();
return $view;
};
Then in my browser, I get this error:
Name | Status
---------------------------- | ------
angular-aside.js | 200
jquery.js | 200
ui-bootstrap-tpls-0.1.3.4.js | 500
My layout.swig header looks like this:
<meta http-equiv="Content-Type" content="text/html" charset="iso-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% if page_title is defined and page_title is not null ? page_title: 'Log in' %}
{% endif %}
<link rel='stylesheet' href='/css/bootstrap.css'/>
<link rel='stylesheet' href='/js/libraries/angular-aside/angular-aside.css'/>
<link rel="stylesheet" href="/js/libraries/angular-notify-master/styles/css/angular-notify-texture.css" id="notifyTheme">
<link rel='stylesheet' href='/css/ivr_builder.css'/>
<link rel="stylesheet" type="text/css" href="/css/custom.css">
<script type="application/javascript" src="/js/libraries/angular.js"></script>
<script type="application/javascript" src="/js/libraries/angular-animate.min.js"></script>
<script type="application/javascript" src="/scripts/ivr_builder/ui-bootstrap-tpls-0.13.4.js"></script>
<script type="application/javascript" src="/js/libraries/angular-aside/angular-aside.js"></script>
<script type="application/javascript" src="/js/libraries/angular-notify-master/scripts/dist/angular-notify.js"></script>
<script type="application/javascript" src="/js/jquery-ui-1.11.4/external/jquery/jquery.js"></script>
[[top_bar]]
Please any suggestion on how why its not loading please on the browser.
I can easily use xsendfile to load it:
Sending files
Sending a file with xsendfile is very straightforward:
<?php
//We want to force a download box with the filename hello.txt
header('Content-Disposition: attachment;filename=hello.txt');
//File is located at /home/username/hello.txt
header('X-Sendfile: /home/username/hello.txt');
You could omit the first header, in which case the browser would not necessarily show a download file dialog. Also, the X-Sendfile header will not show up in the user’s browser, and they will never see the real location of the file they received.
You will not need to send a Content-Length header, as Apache will take care of that for you. Also found this on Slim:
XSendFile On
XSendFilePath "/path/to/directory/containing/js/files"
And in your Slim application route callback, set the X-SendFile header with the absolute path to a file within the file path specified above:
$app->get('/get-file', function () use ($app) {
$res = $app->response();
$res['X-SendFile'] = '/path/to/directory/containing/js/files/foo.js';
});

Warning: React attempted to reuse markup in a container but the checksum was invalid

I'm trying to get an isomorphic Node.js, Express, Webpack, React app working. I'm getting the following error. Any suggestions on how to fix it?
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) rgin:0;display:flex;-webkit-align-items:
(server) rgin:0;display:flex;align-items:center;j
warning # warning.js:45
ReactMount._mountImageIntoNode # ReactMount.js:807
wrapper # ReactPerf.js:66
mountComponentIntoNode # ReactMount.js:268
Mixin.perform # Transaction.js:136
batchedMountComponentIntoNode # ReactMount.js:282
Mixin.perform # Transaction.js:136
ReactDefaultBatchingStrategy.batchedUpdates # ReactDefaultBatchingStrategy.js:62
batchedUpdates # ReactUpdates.js:94
ReactMount._renderNewRootComponent # ReactMount.js:476
wrapper # ReactPerf.js:66
ReactMount._renderSubtreeIntoContainer # ReactMount.js:550
ReactMount.render # ReactMount.js:570
wrapper # ReactPerf.js:66
(anonymous function) # client.jsx:14
(anonymous function) # iso.js:120
each # iso.js:21
bootstrap # iso.js:111
(anonymous function) # client.jsx:12
__webpack_require__ # bootstrap d56606d95d659f2e05dc:19
(anonymous function) # bootstrap d56606d95d659f2e05dc:39
(anonymous function) # bootstrap d56606d95d659f2e05dc:39
This is what is being delivered by the server to the browser initially:
<!doctype html>
<html lang="">
<head>
<title>my title</title>
<meta name="apple-mobile-web-app-title" content="my title" data-react-helmet="true" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" data-react-helmet="true" />
<meta name="apple-mobile-web-app-capable" content="yes" data-react-helmet="true" />
<meta name="mobile-web-app-capable" content="yes" data-react-helmet="true" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" data-react-helmet="true" />
<meta name="description" content="my description." data-react-helmet="true" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" data-react-helmet="true" />
<meta charset="utf-8" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/reset.css" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/base.css" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/Carousel.css" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/main.css" data-react-helmet="true" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed" type="text/css" data-react-helmet="true" />
<link rel="icon" href="/assets/185bb6f691241307862b331970a6bff1.ico" type="image/x-icon" data-react-helmet="true" />
SCRIPT
</head>
<body>
<script src="https://cdn.firebase.com/js/client/2.2.7/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/reactfire/0.4.0/reactfire.min.js"></script>
<div class="app">
<div class="___iso-html___" data-key="_0"><div data-reactid=".1hkqsbm9n9c" data-react-checksum="794698749"><div data-reactid=".1hkqsbm9n9c.0"><div data-reactid=".1hkqsbm9n9c.0.$=10"></div><div style="position:fixed;z-index:2;top:0;left:0;right:0;height:60px;color:rgb(219,219,219);font-family:mainnextcondensed_ultralight;font-size:17px;overflow:hidden;" data-reactid=".1hkqsbm9n9c.0.$/=11"><div style="position:absolute;left:0;top:0;background-color:rgba(27,27,27,0.92);padding-right:35px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10"><div style="float:left;height:60px;width:13px;border-left:5px solid rgb(210,45,164);" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10.$/=10"></div><div style="float:left;height:60px;width:227px;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10.$/=11"></div><div style="display:none;width:0;height:0;border-style:solid;border-width:6px 6px 0 6px;border-color:rgb(117,117,117) transparent transparent transparent;-webkit-transform:rotate(360deg);float:left;margin-left:6px;margin-top:26px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10.$/=12"></div></div><div style="position:absolute;top:0px;left:280px;width:340px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11"><div style="background-color:rgba(27,27,27,0.92);height:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=10"></div><div style="background-color:rgba(53,53,53,0.84);height:40px;position:relative;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11"><div style="position:absolute;top:0;bottom:0;left:0;right:0;padding:0;margin:0;display:flex;align-items:center;justify-content:center;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$=10"><div style="background-image:url('/assets/3bec3e57cb5ee05658440d21984fb7b7.png');background-repeat:no-repeat;background-position:-58px -194px;width:23px;height:22px;position:absolute;top:50%;left:10px;margin-top:-11px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$=10.$icon"></div></div><div style="position:absolute;left:40px;right:40px;top:0px;bottom:0px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$/=12"><input type="text" style="width:100%;height:100%;font-size:14px;font-family:mainnext_regular;background-color:transparent;color:#ffffff;" placeholder="SEARCH ARTISTS, TRACKS, ALBUMS" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$/=12.0"/></div></div><div style="background-color:rgba(27,27,27,0.92);height:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=12"></div></div><div style="position:absolute;top:0px;left:620px;right:0px;background-color:rgba(27,27,27,0.92);height:60px;line-height:60px;overflow:hidden;min-width:500px;padding-left:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12"><div style="position:absolute;top:0px;bottom:0px;right:0px;width:357px;padding-left:141px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0"><a class="" href="/import" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10"><div style="padding-left:40px;position:absolute;left:0px;top:10px;bottom:10px;cursor:pointer;line-height:40px;color:rgb(255,255,255);" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import"><div style="position:absolute;top:0;bottom:0;left:0;right:0;padding:0;margin:0;display:flex;align-items:center;justify-content:center;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import.$=10"><div style="background-image:url('/assets/3bec3e57cb5ee05658440d21984fb7b7.png');background-repeat:no-repeat;background-position:0px -194px;width:28px;height:28px;position:absolute;top:50%;left:0px;margin-top:-14px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import.$=10.$icon"></div></div><span data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import.1">Import Playlists</span></div></a><div style="margin-left:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$admin/=1$admin"><div style="cursor:pointer;float:left;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$admin/=1$admin.$login">Login</div></div></div></div></div></div><noscript data-reactid=".1hkqsbm9n9c.1"></noscript></div></div>
<div class="___iso-state___" data-key="_0" data-meta="{}" data-state=""{\"UserStore\":{\"user\":{\"authenticated\":false,\"isWaiting\":false}},\"SearchStore\":{\"focused\":false,\"input\":\"\",\"timeout\":null,\"searchRequests\":[],\"artists\":null,\"artistsFailed\":false,\"artistsLoading\":false,\"tracks\":null,\"tracksFailed\":false,\"tracksLoading\":false,\"albums\":null,\"albumsFailed\":false,\"albumsLoading\":false,\"playlists\":null,\"playlistsFailed\":false,\"playlistsLoading\":false,\"youtubes\":null,\"youtubesFailed\":false,\"youtubesLoading\":false,\"soundclouds\":null,\"soundcloudsFailed\":false,\"soundcloudsLoading\":false},\"PlayerStore\":{\"player\":null,\"playerSecond\":null,\"playingTrack\":null,\"playingTrackSecond\":null,\"videoId\":null,\"videoIdSecond\":null,\"makingPlayingTrackPlayable\":false,\"radio\":false,\"startSeconds\":0,\"current\":0,\"total\":0,\"perc\":0,\"currentSecond\":0,\"totalSecond\":0,\"percSecond\":0,\"playing\":false,\"playingSecond\":false,\"secondsListened\":0,\"secondsListenedSecond\":0,\"expand\":false,\"source\":null,\"tracksQueue\":[],\"tracksPrevQueue\":[],\"favorite\":false,\"random\":false,\"repeat\":false,\"mute\":false,\"volume\":100,\"mode\":\"standard\"},\"ImportStore\":{\"url\":\"\",\"error\":false,\"focused\":false,\"loading\":false,\"loaded\":false,\"playlist\":null}}""></div>
</div>
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
<!--
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-X', 'auto');
ga('send', 'pageview');
</script>
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.3/fastclick.min.js"></script>
<script type="text/javascript">
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
</script>
<script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
</body>
</html>
This is my server.jsx:
import Iso from 'iso';
import React from 'react';
import ReactDomServer from 'react-dom/server';
import { RoutingContext, match } from 'react-router'
import createLocation from 'history/lib/createLocation';
import alt from 'altInstance';
import routes from 'routes.jsx';
import html from 'base.html';
/*
* #param {AltObject} an instance of the Alt object
* #param {ReactObject} routes specified in react-router
* #param {Object} Data to bootstrap our altStores with
* #param {Object} req passed from Express/Koa server
*/
const renderToMarkup = (alt, state, req, res) => {
let markup, content;
let location = new createLocation(req.url);
alt.bootstrap(state);
match({ routes, location }, (error, redirectLocation, renderProps) => {
if (redirectLocation)
res.redirect(301, redirectLocation.pathname + redirectLocation.search)
else if (error)
res.status(500).send(error.message)
else if (renderProps == null)
res.status(404).send('Not found')
else
content = ReactDomServer.renderToString(<RoutingContext {...renderProps} />);
markup = Iso.render(content, alt.flush());
});
return markup;
};
/*
* Export render function to be used in server/config/routes.js
* We grab the state passed in from the server and the req object from Express/Koa
* and pass it into the Router.run function.
*/
export default function render(state, req, res) {
const markup = renderToMarkup(alt, state, req, res);
return html.replace('CONTENT', markup);
};
And this is my client.jsx:
import React from 'react';
import ReactDOM from 'react-dom';
import Iso from 'iso';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import { Router } from 'react-router';
import alt from 'altInstance';
import routes from 'routes.jsx';
/*
* Client side bootstrap with iso and alt
*/
Iso.bootstrap((state, _, container) => {
alt.bootstrap(state);
ReactDOM.render(<Router history={createBrowserHistory()} children={routes} />, container);
});
And my routes.jsx:
import React from 'react';
import Route from 'react-router';
import App from 'components/App';
import ImportPlaylist from 'components/ImportPlaylist';
import Login from 'components/Login';
import Logout from 'components/Logout';
import Player from 'components/Player/Player';
import Test from 'components/Test';
export default (
<Route path="/" component={App}>
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route name="test" path="test" component={Test} />
<Route name="import" path="import" component={ImportPlaylist} />
<Route name="player" path="/:playlist" component={Player} />
</Route>
);
Note: This applies to older versions of React. If you're using React 16, you should use ReactDOM.hydrate()
Also, the below suggestion will result in a client-side re-render, as suggested by one of the answers below.
This may sound crazily simple, but in your server-side template, wrap your React markup in an extra <div>:
<!-- hypothetical handlebars template -->
<section role="main" class="react-container"><div>{{{reactMarkup}}}</div></section>
Why does this work? On the client, React has a propensity to wrap its rendering of your root component with a superfluous div. ReactDOMServer.render doesn't seem to behave in this manner, thus when one renders into the same container isomorphically, the Adler-32 checksum of your DOM differs.
For those googling and coming here, one weird way of ending up with this issue is where you're not even using isomorphic rendering (i.e. not rendering anything on the server side). This happened with me when using a template with the HtmlWebpackPlugin to process an index.html file.
In my index.html file I already included the bundle.js file myself, and the above plugin also includes another bundle.js via a script-src. Make sure you're setting inject: false to your HtmlWebpackPlugin constructor.
For me killing nodejs completely and restarting worked
WARNING The popular answer here is not correct. What it is doing is removing the existing DOM entirely, and replacing it with a fresh render on the client. It means you lose the fast shallow render from React and are wasting perf, and as a consequence it also swallows the OP error, and any other errors you might have.
Your issue appears to be with CSS - if you are using autoprefixer and inline-styles that would explain your difference here.
The server side has rendered align-items:center and the client has realised it's in a webkit browser and automatically prefixed it for you to -webkit-align-items.
Please post more info about the CSS set up, and any component using inline-styles or similar.
If you are rendering your main content inside of a layout component, you will need to render the layout as static markup (no react attributes) so that the checksum of the content matches between the client and server.
Server:
app.get('/', (req, res) => {
// render the content to a string so it has the same checksum as on the client
// render the layout to static markup so that it does affect the checksum
// this ensures that rendering is isomorphic and client doesn't override server markup
const content = reactDomServer.renderToString(<MyContent />)
const html = '<!DOCTYPE html>' + reactDomServer.renderToStaticMarkup(<HtmlLayout content={content} />)
res.send(html)
})
HtmlLayout:
export default class HtmlLayout extends React.Component<any, any> {
public render () {
return (
<html lang='en'>
<head>
<meta charSet='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>Untitled</title>
<link rel='stylesheet' href='/style/bundle.css' />
</head>
<body>
{ /* insert the content as a string so that it can be rendered separate with its own checksum for proper server-side rendering */ }
<div id='content' dangerouslySetInnerHTML={ {__html: this.props.content} } />
<script src='scripts/bundle.js'></script>
</body>
</html>
)
}
}
Client:
const root = document.getElementById('content')
DOM.render(<MyContent />, root)
Reference: http://jeffhandley.github.io/QuickReactions/20-final-cleanup
In my case the problem was caused by the fact that I was using MediaQuery component from 'react-responsive' without passing the 'value' property which is used by the component when it is not able to access the screen width (e.g. on the Server).
I came across this issue on an Isomorphic app I was working on. What worked for me is, believe it or not, Emptying cache and hard reloading the app on Chrome. Looked like the old DOM was somehow cached on the Browser :)

Node.js and swig template engine - including template inside template

I trying to create main page (part of node.js and mongoDB application) that includes login form.
To add view part I included js files with function that returns HTML, but as I can see much better is using template engine.
Everything is OK until I including one compiled swig part inside another one.
The output of main page is OK, but login part outputs like text on the page.
How is possible to output the login HTML as HTML instead of plain text?
Does more information needed to understand the issue?
Thank you in advance.
var swig = require('swig');
var mainPage_tpl = swig.compileFile(__dirname+'/../views/mainpage_tpl.html');
var formLogin_tpl = swig.compileFile(__dirname+'/../views/login_tpl.html');
var loginOutput = formLogin_tpl();
var mainPageOutput = mainPage_tpl({
title: 'Sometitle',
pagetitle: 'Somepagetitle',
content: loginOutput
});
exports.get = function(req, res){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(mainPageOutput);
res.end();
}
mainpage_tpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/assets/reset.css" />
<link rel="stylesheet" href="/assets/style.css" />
<script type="text/javascript" src="/assets/jquery-2.0.0.js"></script>
<script type="text/javascript" src="/assets/script.js"></script>
</head>
<body id="login_page">
<h1>{{pagetitle}}</h1>
<div id="content">{{content}}</div>
</body>
</html>
If you want to include literal HTML, you need to tell Swig to not escape it using the safe filter:
...
<div id="content">{{ content|safe }}</div>
...

Resources