# create-react-app recipes
- [General](#general)
* [Prerender website without ejecting](#prerender-website-without-ejecting)
* [Preact without ejecting](#preact-without-ejecting)
* [Split in chunks](#split-in-chunks)
* [Configure sw-precache without ejecting](#configure-sw-precache-without-ejecting)
* [Use sw-precache with Google Analytics](#use-sw-precache-with-google-analytics)
* [Add Appcache](#add-appcache)
* [Meta tags](#meta-tags)
* [The Perfect 404](#the-perfect-404)
- [Hosting on AWS S3 + cloudflare.com](#hosting-on-aws-s3--cloudflarecom)
* [Setup Cloudflare](#setup-cloudflare)
* [Deployment](#deployment)
* [Caveats](#caveats)
- [react-snap specific](#react-snap-specific)
* [Usage with Google Analytics](#usage-with-google-analytics)
* [Use to render screenshots](#use-to-render-screenshots)
## General
### Prerender website without ejecting
Use [react-snap](https://github.com/stereobooster/react-snap/blob/master/Readme.md#basic-usage-with-create-react-app) ;)
### Preact without ejecting
Full example is [here](https://github.com/stereobooster/an-almost-static-stack/blob/react-snap/scripts/build-preact.js).
```sh
yarn add preact preact-compat
````
`scripts/build-preact.js`:
```js
process.env.NODE_ENV = "production"
const config = require("react-scripts/config/webpack.config.prod")
config.resolve.alias["react"] = "preact-compat"
config.resolve.alias["react-dom"] = "preact-compat"
require("react-scripts/scripts/build")
```
### Split in chunks
With webpack 2+ you can use dynamic `import` to split bundles in chunks. See articles:
- http://thejameskyle.com/react-loadable.html
- https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
### Configure sw-precache without ejecting
Full example is [here](https://github.com/stereobooster/an-almost-static-stack/blob/react-snap/scripts/sw-precache-config.js).
Tip: See [material design offline states](https://material.io/guidelines/patterns/offline-states.html) for UI advices on offline applications. Also see section about [snackbars & toasts](https://material.io/guidelines/components/snackbars-toasts.html).
`package.json`:
```json
"scripts": {
"generate-sw": "sw-precache --root=build --config scripts/sw-precache-config.js && uglifyjs build/service-worker.js -o build/service-worker.js",
"build-snap": "react-scripts build && react-snap && yarn run generate-sw"
}
```
`scripts/sw-precache-config.js`:
```js
module.exports = {
// a directory should be the same as "reactSnap.destination",
// which default value is `build`
staticFileGlobs: [
"build/static/css/*.css",
"build/static/js/*.js",
"build/shell.html",
"build/index.html"
],
stripPrefix: "build",
publicPath: ".",
// there is "reactSnap.include": ["/shell.html"] in package.json
navigateFallback: "/shell.html",
// Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/],
// By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./,
// configuration specific to this experiment
runtimeCaching: [
{
urlPattern: /api/,
handler: "fastest"
}
]
};
```
You can use `200.html` instead of `shell.html` if you use `react-snap` and do not have separate `shell.html`. This is important because `react-snap` will prerender `index.html` and when user will be offline their will see a flash of `index.html` on navigation.
### Use sw-precache with Google Analytics
See this article https://developers.google.com/web/updates/2016/07/offline-google-analytics
### Add Appcache
Full example is [here](https://github.com/stereobooster/an-almost-static-stack/blob/react-snap/scripts/generate-appcache.js)
[Webkit promises to add Service Worker support](https://webkit.org/status/#specification-service-workers) meantime we can use Appcache.
Tip: you can prompt user to "install your site as web app", like [this](https://www.npmjs.com/package/angular-add-to-home-screen).
Tip 2: you may want something like [localForage](https://localforage.github.io/localForage/) to save data on client side
```sh
yarn add appcache-nanny
```
copy [`appcache-loader.html`](https://github.com/gr2m/appcache-nanny/blob/master/appcache-loader.html) to `public/`.
`scripts/generate-appcache.js`:
```js
const SW_PRECACHE_CONFIG = './sw-precache-config'
const OUT_FILE = '../build/manifest.appcache'
const glob = require('globby')
const { staticFileGlobs, stripPrefix, navigateFallback } = require(SW_PRECACHE_CONFIG)
const fs = require('fs')
const path = require('path')
glob(staticFileGlobs).then(files => {
// filter out directories
files = files.filter(file => fs.statSync(file).isFile())
// strip out prefix
files = files.map(file => file.replace(stripPrefix, ''))
const index = files.indexOf(navigateFallback);
if (index > -1) {
files.splice(index, 1);
}
const out = [
'CACHE MANIFEST',
`# version ${ new Date().getTime() }`,
'',
'CACHE:',
...files,
'',
'NETWORK:',
'*',
'http://*',
'https://*',
'',
'FALLBACK:',
`/ ${navigateFallback}`
].join('\n')
fs.writeFileSync(path.join(__dirname, OUT_FILE), out)
console.log(`Wrote ${OUT_FILE} with ${files.length} resources.`)
})
```
`registerServiceWorker.js`:
```js
import appCacheNanny from "appcache-nanny";
export default function register() {
if (process.env.NODE_ENV !== 'production') return false;
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
});
} else if (window.applicationCache) {
appCacheNanny.start();
appCacheNanny.on('updateready', () => {
console.log('New content is available; please refresh.');
});
appCacheNanny.on('cached', () => {
console.log('Content is cached for offline use.');
});
}
return true;
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
} else if (window.applicationCache) {
appCacheNanny.stop();
}
}
```
### Meta tags
Full example is [here](https://github.com/stereobooster/an-almost-static-stack/blob/react-snap/src/components/Seo.js).
Tip: If you do not have images for social media, you can use screenshots of your website. See [Use to render screenshots](#use-to-render-screenshots) section.
```sh
yarn add react-helmet
```
```js
import React from 'react'
import Helmet from 'react-helmet'
import { basePath } from './Config.js';
const locales = {
"en": "en_US"
}
const Meta = (data) => {
const lang = data.lang || "en"
const title = data.title
const description = data.description
const image = data.image !== undefined && `${basePath}${data.image}`
const canonical = data.canonical !== undefined && `${basePath}${data.canonical}`
const type = data.type === undefined ? "article" : "website"
const width = data.image && (data.width || 1200)
const height = data.image && (data.height || 630)
return (