Last active
November 7, 2017 08:07
-
-
Save danalloway/dc8b87647b7c64ed9746cd632f95f53a to your computer and use it in GitHub Desktop.
preact, redux, react-router-redux SSR
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { h } from 'preact'; | |
import Link from 'react-router-dom/Link'; | |
import Route from 'react-router-dom/Route'; | |
import Switch from 'react-router-dom/Switch'; | |
import Home from '../routes/Home'; | |
import About from '../routes/About'; | |
const App = () => ( | |
<div> | |
<Link to="/">Home</Link> | |
<Link to="/about">About</Link> | |
<Switch> | |
<Route exact path="/" component={Home} /> | |
<Route path="/about" component={About} /> | |
</Switch> | |
</div> | |
); | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { h } from 'preact'; | |
import { Provider } from 'preact-redux'; | |
import { ConnectedRouter } from 'react-router-redux'; | |
import { persistStore } from 'redux-persist'; | |
import PersistGate from './components/PersistGate'; | |
import App from './components/app'; | |
import configureStore from './state/store'; | |
import createHistory from './state/history'; | |
const history = createHistory(); | |
const store = configureStore(history); | |
const persistor = persistStore(store); | |
const Client = () => ( | |
<Provider store={store}> | |
<PersistGate persistor={persistor}> | |
<ConnectedRouter history={history}> | |
<App /> | |
</ConnectedRouter> | |
</PersistGate> | |
</Provider> | |
); | |
export default Client; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"hosting": { | |
"public": "build", | |
"rewrites": [ | |
{ | |
"source": "**", | |
"function": "app" | |
} | |
], | |
"headers": [ | |
{ | |
"source": "**", | |
"headers": [ | |
{ | |
"key": "Cache-Control", | |
"value": "public, max-age=3600, no-cache" | |
}, | |
{ | |
"key": "Access-Control-Max-Age", | |
"value": "600" | |
} | |
] | |
}, | |
{ | |
"source": "/sw.js", | |
"headers": [ | |
{ | |
"key": "Cache-Control", | |
"value": "private, no-cache" | |
} | |
] | |
}, | |
{ | |
"source": "**/*.chunk.*.js", | |
"headers": [ | |
{ | |
"key": "Cache-Control", | |
"value": "public, max-age=31536000" | |
} | |
] | |
}, | |
{ | |
"source": "/", | |
"headers": [ | |
{ | |
"key": "Link", | |
"value": "</bundle.16517.js>; rel=preload; as=script, </style.aab19.css>; rel=preload; as=style" | |
} | |
] | |
}, | |
{ | |
"source": "/Home", | |
"headers": [ | |
{ | |
"key": "Link", | |
"value": "</bundle.16517.js>; rel=preload; as=script, </style.aab19.css>; rel=preload; as=style, </route-Home.chunk.3bba7.js>; rel=preload; as=script" | |
} | |
] | |
}, | |
{ | |
"source": "/About", | |
"headers": [ | |
{ | |
"key": "Link", | |
"value": "</bundle.16517.js>; rel=preload; as=script, </style.aab19.css>; rel=preload; as=style, </route-About.chunk.ddd48.js>; rel=preload; as=script" | |
} | |
] | |
} | |
] | |
}, | |
"functions": { | |
"source": "functions" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// static (server) version of the app must use the in-memory history | |
const createBrowserHistory =import createBrowserHistory from 'history/createBrowserHistory'; | |
import createMemoryHistory from 'history/createMemoryHistory'; | |
export default opts => | |
typeof window === 'undefined' | |
? createMemoryHistory(opts) | |
: createBrowserHistory(opts); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is the main entry point of my Preact application. | |
* We will need to render Static (server) and Client versions. | |
*/ | |
import { h } from 'preact'; | |
import Client from './client'; | |
import Static from './static'; | |
import './style'; | |
const Root = ({ history, store, url }) => ( | |
<div id="app"> | |
{typeof window === 'undefined' ? ( | |
<Static history={history} store={store} url={url} /> | |
) : ( | |
<Client /> | |
)} | |
</div> | |
); | |
export default Root; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Our SSR code will need to access a compiled version of our application. | |
* The three entries below (the ssr-bundle is already provided by preact-cli) | |
* will provide the parts our SSR code will need to require later. | |
*/ | |
export default function(config, env, helpers) { | |
/** | |
* We need to access our Redux store / history code on the server. | |
* so generate transpiled bundles so we can easily `require` them later | |
*/ | |
if (env.ssr) { | |
config.entry = { | |
'ssr-bundle': env.source('index.js'), | |
'ssr-history': env.source('state/history.js'), | |
'ssr-store': env.source('state/store.js') | |
}; | |
config.output.filename = '[name].js'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* I'm using Firebase Functions to SSR my application. | |
*/ | |
// Firebase | |
const functions = require('firebase-functions'); | |
// Node | |
const fs = require('fs'); | |
const { resolve } = require('path'); | |
// Preact | |
const { h } = require('preact'); | |
const render = require('preact-render-to-string'); | |
// Express Server | |
const app = require('express')(); | |
// Our application (note the three ssr-* bundles we're using here) | |
const dir = resolve(__dirname, 'build/ssr-build'); | |
const bundle = require(dir + '/ssr-bundle').default; | |
const createHistory = require(dir + '/ssr-history').default; | |
const configureStore = require(dir + '/ssr-store').default; | |
const App = bundle; | |
const RGX = /<div id="app"[^>]*>.*?(?=<script)/i; | |
const template = fs.readFileSync(resolve(__dirname, 'build/index.html'), 'utf8'); | |
function renderApplication(req, res) { | |
const url = req.url; | |
// this will feed the current URL from Express to the in-memory store that Redux and the Router will use | |
const history = createHistory({ initialEntries: [url] }); | |
const store = configureStore(history); | |
const body = render(h(App, { history, store, url })); | |
const preloadedState = JSON.stringify(store.getState()).replace(/</g, '\\u003c'); | |
const html = template.replace(RGX, `${body}<script>window.__PRELOADED_STATE__ = ${preloadedState}</script>`); | |
res.send(html); | |
} | |
app.get('*', (req, res) => renderApplication(req, res)); | |
exports.app = functions.https.onRequest(app); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { h } from 'preact'; | |
import { Provider } from 'preact-redux'; | |
import { ConnectedRouter } from 'react-router-redux'; | |
import createHistory from './state/history'; | |
import configureStore from './state/store'; | |
import App from './components/app'; | |
// we have to provide a default value for `history` and `store` here | |
// as `preact build` will attempt it's on pass at SSRing our app | |
// and it wont't have either of those things to use, and fail | |
const Static = ({ history = createHistory(), store, url = '/' }) => ( | |
<Provider store={store || configureStore(history)}> | |
<ConnectedRouter location={url} history={history}> | |
<App /> | |
</ConnectedRouter> | |
</Provider> | |
); | |
export default Static; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createStore, applyMiddleware, combineReducers } from 'redux'; | |
import { routerMiddleware, routerReducer } from 'react-router-redux'; | |
import { createLogger } from 'redux-logger'; | |
import { persistReducer } from 'redux-persist'; | |
import appReducer from './modules'; | |
import persistConfig from './persistConfig'; | |
export default history => { | |
let reducers = combineReducers({ | |
...appReducer, | |
router: routerReducer | |
}); | |
let middleware = [routerMiddleware(history)]; | |
let preloadedState; | |
if (typeof window !== 'undefined') { | |
reducers = persistReducer(persistConfig, reducers); | |
middleware = [ | |
...middleware, | |
createLogger({ | |
collapsed: true, | |
duration: true | |
}) | |
]; | |
preloadedState = window.__PRELOADED_STATE__; | |
delete window.__PRELOADED_STATE__; | |
} | |
const store = createStore( | |
reducers, | |
preloadedState, | |
applyMiddleware(...middleware) | |
); | |
return store; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment