Skip to content

Instantly share code, notes, and snippets.

@danalloway
Last active November 7, 2017 08:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save danalloway/dc8b87647b7c64ed9746cd632f95f53a to your computer and use it in GitHub Desktop.
Save danalloway/dc8b87647b7c64ed9746cd632f95f53a to your computer and use it in GitHub Desktop.
preact, redux, react-router-redux SSR
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;
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;
{
"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"
}
}
// 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 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;
/**
* 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';
}
}
/**
* 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);
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;
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