ããã«ã¡ã¯ã
Googleã®æ€çŽ¢ã³ã³ãœãŒã«ã«ãã°ã€ã³ããŠGoogleãšããŠååŸãããšããŠã§ããµã€ãã空çœã®ããŒãžãšããŠè¡šç€ºããããšããåé¡ã«æ°ã¥ããŸããã
ç§ã¯ããã€ãã®èª¿æ»ãè¡ããŸããããããŠããã®ããã€ãã¯ç§ããã®èšäºã«å°ããŸãïŒ
https://medium.com/@gajus/react -application-seen-as-a-blank-page-via-fetch-as-google-afb11dff8562
ããã¯æ€çŽ¢ãšã³ãžã³ã§ããå¯èœæ§ãéåžžã«é«ããã°ãŒã°ã«èªäœãåãããã«åäœããªãã®ã§ãã§ããããŸããããããªããšãä»ãŸã§ã«ã©ã³ãã³ã°ã倱ã£ãŠããã§ãããããããã§ãããŠã§ããµã€ãã®ææè ãç®èŠããããŠããŸãã®ã§ããã®åé¡ã解決ããããšæããŸãã
phantomjsãããŠã³ããŒãããŸããããããã¯Googleãšåããã§ããã§ãããšæ³å®ããŠãããã¬ã³ããªã³ã°ããããšãããšãããã€ãã®ãšã©ãŒãè¿ãããŸãã
ãã®åé¡ã¯ãã³ã³ãã€ã«ããã¿ãŒã²ãããã©ãŠã¶ãå€æŽããã®ãšåããããç°¡åã«è§£æ±ºã§ããå ŽåããããŸãããbabel / polyfilãã€ã³ã¹ããŒã«ããŠã€ã³ããŒãããã ãã§ã¯æ©èœããŸããã
誰ããç§ãããã§æ£ããæ¹åã«åããããšãã§ããã§ããããïŒ
ããŒãžã空çœãååŸããŠããå Žåã¯ãSSRãŸãã¯ãã€ãã¬ãŒã·ã§ã³ã¹ããŒã ãå£ããŠããŸãã Razzleã¯ãç®±ããåºããŠèª¬æããå 容ã«æ©ãŸãããããšã¯ãããŸãããå®éãBBC.comã®ãããªSEOã«çŠç¹ãåœãŠããµã€ãã§äœ¿çšãããŠããŸãã server.jsãã¡ã€ã«ã貌ãä»ããããšãã§ããŸããïŒ ãµãŒããŒã§ããŒã¿ãååŸããŠããŸããïŒ
ããã«ã¡ã¯ãžã£ã¬ãã
è¿ éãªè¿ä¿¡ãããããšããã¯ãããµãŒããŒããããŒã¿ããã§ããããŸãã
ãããç§ã®server.jsã§ã
import App from './App';
import React from 'react';
import Helmet from 'react-helmet';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import express from 'express';
import compression from 'compression';
import { renderToString } from 'react-dom/server';
import configureStore from './store/configureStore';
import { remoteLoader } from './api/remoteLoader';
import serialize from 'serialize-javascript';
import { Capture } from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';
import { IS_PRODUCTION } from './components/shared/constants';
const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
const server = express();
server.use(compression());
if (!IS_PRODUCTION) {
server.set('cache', false);
}
server
.disable('x-powered-by')
.use(express.static(process.env.RAZZLE_PUBLIC_DIR, { Expires: '30d' }))
.get('/*', (req, res) => {
//Ensures clients with old css paths are served the current file
if (req.path.indexOf('/static/css') > -1 && assets.client.css) {
const currentCssFile = `${process.env.RAZZLE_PUBLIC_DIR}${assets.client.css}`;
return res.sendFile(currentCssFile);
}
remoteLoader(apiResult => {
const responseCode = typeof apiResult.status === 'undefined' ? 404 : apiResult.status;
if (responseCode === 301) {
return res.redirect(responseCode, apiResult.headers.location);
}
// Compile an initial state
const initialState = {
remote: {
cms: {
result: apiResult ? apiResult.data : false,
loading: false
},
myDrewberry: {
searchResults: null,
loading: false,
failed: false
}
}
};
// Create a new Redux store instance
const store = configureStore(initialState);
const context = {};
const modules = [];
const markup = renderToString(
<Capture report={moduleName => modules.push(moduleName)}>
<StaticRouter context={context} location={req.url}>
<Provider store={store}>
<App />
</Provider>
</StaticRouter>
</Capture>
);
const helmet = Helmet.renderStatic();
if (context.url) {
res.redirect(context.url);
} else {
const bundles = getBundles(stats, modules);
const chunks = bundles.filter(bundle => bundle.file.endsWith('.js'));
res.status(responseCode).send(
`<!doctype html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${assets.client.css ? `<link rel="stylesheet" href="${assets.client.css}">` : ''}
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
<link rel="icon" type="image/png" href="/favicon16.png" sizes="16x16"/>
<link rel="icon" type="image/png" href="/favicon32.png" sizes="32x32"/>
<link rel="icon" type="image/png" href="/favicon96.png" sizes="96x96"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/> <!--320-->
<meta http-equiv="expires" content="0">
</head>
<body class="drewberry-preload">
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TKPXWB');</script>
<!-- End Google Tag Manager -->
<div id="root">${markup}</div>
<script>
window.__PRELOADED_STATE__ = ${serialize(initialState)}
</script>
${
IS_PRODUCTION
? `<script src="${assets.client.js}"></script>`
: `<script src="${assets.client.js}" crossorigin></script>`
}
${chunks
.map(chunk =>
IS_PRODUCTION
? `<script src="/${chunk.file}"></script>`
: `<script src="http://${process.env.HOST}:${parseInt(process.env.PORT, 10) + 1}/${
chunk.file
}"></script>`
)
.join('\n')}
<script>window.main();</script>
</body>
</html>`
);
}
}, req.path);
});
export default server;
ç®±ããåºããŠRazzleã«åé¡ããªãããšã確èªã§ããŸããã¯ãªãŒã³ã€ã³ã¹ããŒã«ãããŒãããphantomjsã䜿çšãããšãèŠåã¯è¡šç€ºãããŸããã§ãããããã¯ãããããã©ã€ãã©ãªãã€ã³ã¹ããŒã«ãããã®ã§ããããšãæå³ããŸãã
äžèšã§æãããªããšã¯ãããŸããïŒ
ä»ã¯ã¬ãã§ãŠã®è¿œè·¡ã®ããã§ãïŒ :-)
ãããã£ãŠãç§ã®ææ¡ïŒãããŠããããããã«é¢ããããã¥ã¡ã³ãã®ãã©ãã«ã·ã¥ãŒãã£ã³ã°ã¬ã€ããå¿
èŠã§ãïŒã¯ã server.js
ã client.js
ã App.js
ããããŠæœåšçã«ã¯ã§ããã ãå€ãã³ã¡ã³ãã¢ãŠãããããšã§ãWebpackã®ã¢ã¹ãã¯ãïŒååšããå ŽåïŒããªãŒããŒã©ã€ãããŠãRazzleã¢ããªã±ãŒã·ã§ã³ããHelloWorldãã®ãããªç¶æ
ã«æ»ããŸãã ãŸãããã¹ãŠã®ã¹ã¯ãªãããç®±ããåºããŠæ£ããããŒããããŠããããšã確èªããããã«ãRazzleã®HTMLãã³ãã¬ãŒããserver.js
ãã¡ã€ã«ã«ã³ããŒããŠæ»ãããšããå§ãããŸãã å®è¡ããããChromeéçºããŒã«ãéãã[ããã©ãŒãã³ã¹]ã¿ãã«ç§»åãã[ã¹ã¯ãªãŒã³ã·ã§ãã]ãã§ãã¯ããã¯ã¹ããªã³ã«ããŠã[æŽæ°]ã¢ã€ã³ã³ãæŒããŸãã 以äžã®GIFãåç
§ããŠãã ããã
ç®æšã¯ãã¿ã€ã ã©ã€ã³é åã«ç©ºçœã®çœããã¬ãŒã ããªãããšã確èªããããšã§ãã ãã©ãã·ã¥ãªãã§é©åãªã¬ã³ããªã³ã°ãååŸããããå€ãã³ãŒããããã«è¿œå ããå¿ èŠããããŸãã èµ€ã¡ããã®äžæ©ãèžã¿åºããŸãã 泚æããªããšãããã¯éåžžã«ç°¡åã«æ··ä¹±ããŸãã
ããªãã®ç¹å®ã®ã±ãŒã¹ã«é¢ããŠã¯ãç§ã¯ããªããããŠãããã¹ãŠã®ã°ãŒã°ã«ã¿ã°ãããŒãžã£ãŒã®ãã®ãããªãã®CSSããããŠã©ããªã³ãŒãåå²ãåé€ããããšããå§ããŸãã ã¬ã³ããªã³ã°ããŠéæ¹åã«åäœãããã ãã§ãã 次ã«ãReduxãåäœãããŸãã 次ã«ãreact-helmetãè¿œå ãçŽããåé¡ããªããšæã£ãããLighthouseãã¹ããå®è¡ããŠå確èªããŸãïŒ[ç£æ»]ã¿ãïŒã ãã®ç¶æ ãæ°ãããã©ã³ãã«ã³ãããããŠãå°æ¥ãã®ç¶æ ã«æ»ãããšãã§ããããã«ããŸãã 次ã«ãåæã¹ã¯ãªãããè¿œå ãçŽããŠãã¬ã³ããªã³ã°ãå床確èªããŸãã æåŸã«ãã³ãŒãåå²ãè¿œå ãçŽããŠã¿ãŠãã ããã
泚ïŒcssãä¿æããRazzleãéçºã¢ãŒãã§äžèšã®åæãè¡ããšãRazzleã¯éçºäžã«ãµãŒããŒïŒã¯ã©ã€ã¢ã³ãã®ã¿ïŒã§
.css
ã¹ã¿ã€ã«ãåŠçããªããããFOUCãçºçããå¯èœæ§ããããŸãã ãããã£ãŠã.css
ãã¡ã€ã«ã䜿çšããŠããå Žåã«ã¹ã¿ã€ã«ãã€ãã¬ãŒã·ã§ã³ã䜿çšããŠé©åãªSSRãå®è¡ããŠããããšãå®å šã«ãã¹ãããã«ã¯ãæ¬çªçšã«Razzleããã«ãããæ¬çªãµãŒããŒãããŒã«ã«ã§å®è¡ããå¿ èŠããããŸãã ãã ãããµãŒããŒããHTMLã®ååšã確èªããã ãã®å Žåã¯ããããéçºã¢ãŒãã§å®è¡ã§ããŸãã
ããŠãRazzleãšã®çŽ æŽãããä»äºã«æè¬ããŸãããããŠä»ããã«æè¬ããŸãã ç§ã¯ãããããŸãç£æ»ããå ±åããã€ããã§ãã
ãã¡ãããã空ã®ãïŒç©ºçœã§ã¯ãªãããšã©ãŒã§ãïŒseoããŒãžãæåã«ãã§ãã¯ããã®ã¯ã衚瀺ããããŒã¿ãããŒãããããã«ããŒãžãcomponentDidMount
ã«äŸåããŠãããã©ããã§ãã curl
ã䜿çšããŠãã°ãã確èªã§ããŸãã ã¯ãã®å ŽåãããªããŒãããã«ã¯After.jsã®ãããªãã®ãå¿
èŠã§ãã
1000msããŒã¯ã®åšãã«ç©ºçœã®ããŒãžãããå Žåã[ããã©ãŒãã³ã¹]ã¿ãã䜿çšããŠãããã°ããæ¹æ³ãã¢ããã€ã¹ã§ããŸããïŒ ç§ã®åé¡ã¯ãã¢ã€ã³ã³ãã©ã³ãã©ã€ãã©ãªããµãŒããŒã«ããŒããããªãããšã§ããããããã£ãŠãåæããŒãæã«ãããã®ç©ºçœã®ç©ºã®æ£æ¹åœ¢ã衚瀺ãããããšãç¥ã£ãŠããŸã...
ãããç§ã®ã«ã¹ã¿ã ãã©ã³ãã¯ãã¹ãŠããã£ãšåŸã«ãªããŸã§èªã¿èŸŒãŸããŸããã CSSã®@ font-face {}ãä»ããŠãã©ã³ããããŒãããŠããŸãã
@font-face {
font-family: 'SFProText';
font-display: auto;
src: url('fonts/SFPro/SF-Pro-Display-Regular.otf') format('truetype');
font-weight: normal;
font-style: normal;
}
@jaredpalmerç§ãåãåé¡ãæ±ããŠããŸãã ãã©ã³ããšã¢ã»ãããã¯ã©ã€ã¢ã³ãããŒãžã«èªã¿èŸŒãŸããŸããã
ã¢ããªã±ãŒã·ã§ã³ã®ã»ããã¢ããã«webpack.configãã¡ã€ã«ãè¿œå ããŠããŸãããããã¯ããªããžããªããè€è£œãããã®ã§ãã ãªããžããªããã³ããŒãååŸããŠnpmã¢ãžã¥ãŒã«ãã€ã³ã¹ããŒã«ããåŸãnpmstartã³ãã³ããå®è¡ããŠã¢ããªã±ãŒã·ã§ã³ã®äœ¿çšãéå§ããŸããã
èšå®ã«å€±æãããã®ããŸãã¯ãã®åé¡ã解決ããããã«æ°ããwebpackãã¡ã€ã«ã䜿çšããå¿
èŠããããã®ã¯ãããŸããïŒ
ãã®åé¡ã®è§£æ±ºã«ãååãã ããã
ç§ã®App.cssãã¡ã€ã«ã¯æ¬¡ã®ããã«ãªããŸãã
@ font-face {
font-familyïŒ 'Roboto-medium';
srcïŒurlïŒ "./ static / fonts / Roboto-Medium-webfont.eot"ïŒ;
srcïŒurlïŒ "./ static / fonts / Roboto-Medium-webfont.eotïŒ#iefix"ïŒformatïŒ "embedded-opentype"ïŒãurlïŒ "./ static / fonts / Roboto-Medium-webfont.woff"ïŒ formatïŒ "woff"ïŒãurlïŒ "./ static / fonts / Roboto-Medium-webfont.ttf"ïŒformatïŒ "truetype"ïŒ; }
@ font-face {
font-familyïŒ 'Roboto-regular';
srcïŒurlïŒ "./ static / fonts / roboto-regular-webfont.eot"ïŒ;
srcïŒurlïŒ "./ static / fonts / roboto-regular-webfont.eotïŒ#iefix"ïŒformatïŒ "embedded-opentype"ïŒãurlïŒ "./ static / fonts / roboto-regular-webfont.woff"ïŒ formatïŒ "woff"ïŒãurlïŒ "./ static / fonts / roboto-regular-webfont.ttf"ïŒformatïŒ "truetype"ïŒ; }
ããããšãinadvane :)
æãåèã«ãªãã³ã¡ã³ã
ãããã£ãŠãç§ã®ææ¡ïŒãããŠããããããã«é¢ããããã¥ã¡ã³ãã®ãã©ãã«ã·ã¥ãŒãã£ã³ã°ã¬ã€ããå¿ èŠã§ãïŒã¯ã
server.js
ãclient.js
ãApp.js
ããããŠæœåšçã«ã¯ã§ããã ãå€ãã³ã¡ã³ãã¢ãŠãããããšã§ãWebpackã®ã¢ã¹ãã¯ãïŒååšããå ŽåïŒããªãŒããŒã©ã€ãããŠãRazzleã¢ããªã±ãŒã·ã§ã³ããHelloWorldãã®ãããªç¶æ ã«æ»ããŸãã ãŸãããã¹ãŠã®ã¹ã¯ãªãããç®±ããåºããŠæ£ããããŒããããŠããããšã確èªããããã«ãRazzleã®HTMLãã³ãã¬ãŒããserver.js
ãã¡ã€ã«ã«ã³ããŒããŠæ»ãããšããå§ãããŸãã å®è¡ããããChromeéçºããŒã«ãéãã[ããã©ãŒãã³ã¹]ã¿ãã«ç§»åãã[ã¹ã¯ãªãŒã³ã·ã§ãã]ãã§ãã¯ããã¯ã¹ããªã³ã«ããŠã[æŽæ°]ã¢ã€ã³ã³ãæŒããŸãã 以äžã®GIFãåç §ããŠãã ãããç®æšã¯ãã¿ã€ã ã©ã€ã³é åã«ç©ºçœã®çœããã¬ãŒã ããªãããšã確èªããããšã§ãã ãã©ãã·ã¥ãªãã§é©åãªã¬ã³ããªã³ã°ãååŸããããå€ãã³ãŒããããã«è¿œå ããå¿ èŠããããŸãã èµ€ã¡ããã®äžæ©ãèžã¿åºããŸãã 泚æããªããšãããã¯éåžžã«ç°¡åã«æ··ä¹±ããŸãã
ããªãã®ç¹å®ã®ã±ãŒã¹ã«é¢ããŠã¯ãç§ã¯ããªããããŠãããã¹ãŠã®ã°ãŒã°ã«ã¿ã°ãããŒãžã£ãŒã®ãã®ãããªãã®CSSããããŠã©ããªã³ãŒãåå²ãåé€ããããšããå§ããŸãã ã¬ã³ããªã³ã°ããŠéæ¹åã«åäœãããã ãã§ãã 次ã«ãReduxãåäœãããŸãã 次ã«ãreact-helmetãè¿œå ãçŽããåé¡ããªããšæã£ãããLighthouseãã¹ããå®è¡ããŠå確èªããŸãïŒ[ç£æ»]ã¿ãïŒã ãã®ç¶æ ãæ°ãããã©ã³ãã«ã³ãããããŠãå°æ¥ãã®ç¶æ ã«æ»ãããšãã§ããããã«ããŸãã 次ã«ãåæã¹ã¯ãªãããè¿œå ãçŽããŠãã¬ã³ããªã³ã°ãå床確èªããŸãã æåŸã«ãã³ãŒãåå²ãè¿œå ãçŽããŠã¿ãŠãã ããã