-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 897f7af
Showing
19 changed files
with
6,346 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
logs | ||
*.log | ||
npm-debug.log* | ||
.DS_Store | ||
|
||
coverage | ||
node_modules | ||
build | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# SSR Experiments with React Router 4 | ||
|
||
## Install | ||
|
||
Clone the repo and install deps... | ||
|
||
``` | ||
git clone ... | ||
cd ssr-demo | ||
yarn install & yarn start | ||
``` | ||
|
||
## What is going on here? | ||
|
||
This little app demonstrates some cool SSR stuff you can do with React Router 4: | ||
|
||
- Next.js-like data fetching using an HoC, static route config, and react-router-config. | ||
- "Client-only" routes...this translates to partial/selective SSR (because Routes are just components :wink:) | ||
- Using RR4's `statusContext` to set HTTP status codes isomorphically. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "my-razzle-app", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
"scripts": { | ||
"start": "razzle start", | ||
"build": "razzle build", | ||
"test": "razzle test --env=jsdom", | ||
"start:prod": "NODE_ENV=production node build/server.js" | ||
}, | ||
"dependencies": { | ||
"express": "^4.15.4", | ||
"react": "^15.6.1", | ||
"react-dom": "^15.6.1", | ||
"react-router-dom": "^4.2.2" | ||
}, | ||
"devDependencies": { | ||
"razzle": "^0.7.6-rc2" | ||
} | ||
} |
Binary file not shown.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
User-agent: * | ||
|
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
*, | ||
*:before, | ||
*:after { | ||
box-sizing: border-box; | ||
} | ||
html, | ||
body, | ||
#root { | ||
height: 100%; | ||
margin: 0; | ||
} | ||
|
||
body { | ||
font-family: system-ui, 'San Francisco', -apple-system, BlinkMacSystemFont, | ||
'.SFNSText-Regular', 'Helvetica Neue', Helvetica, sans-serif; | ||
color: #222; | ||
font-size: 16px; | ||
background-color: #fff; | ||
margin: 0; | ||
padding: 0; | ||
font-feature-settings: "liga", "kern"; | ||
text-rendering: optimizeLegibility; | ||
-webkit-font-smoothing: antialiased; | ||
} | ||
|
||
.App { | ||
margin: 1rem; | ||
} | ||
|
||
a { | ||
color: #0af; | ||
text-decoration: none; | ||
-webkit-user-select: text; | ||
} | ||
|
||
a:hover { | ||
cursor: pointer; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import './App.css'; | ||
|
||
import NavLink from 'react-router-dom/NavLink'; | ||
import React from 'react'; | ||
import Route from 'react-router-dom/Route'; | ||
import Switch from 'react-router-dom/Switch'; | ||
|
||
const App = ({ routes, initialData }) => { | ||
return routes | ||
? <div className="App"> | ||
<nav> | ||
{routes.map((route, index) => | ||
<NavLink | ||
style={{ marginRight: '1rem', color: '#0af' }} | ||
activeStyle={{ fontWeight: 800, color: '#000' }} | ||
key={`nav-${index}`} | ||
exact={index === 0} | ||
to={route.path} | ||
> | ||
{route.name} | ||
</NavLink> | ||
)} | ||
</nav> | ||
<Switch> | ||
{routes.map((route, index) => { | ||
// pass in the initialData from the server or window.DATA for this | ||
// specific route | ||
return ( | ||
<Route | ||
key={index} | ||
path={route.path} | ||
exact={route.exact} | ||
render={props => | ||
React.createElement(route.component, { | ||
...props, | ||
initialData: initialData[index] || null, | ||
})} | ||
/> | ||
); | ||
})} | ||
</Switch> | ||
</div> | ||
: null; | ||
}; | ||
|
||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import App from './App'; | ||
import BrowserRouter from 'react-router-dom/BrowserRouter'; | ||
import React from 'react'; | ||
import { render } from 'react-dom'; | ||
import routes from './routes'; | ||
|
||
const data = window._INITIAL_DATA_; | ||
|
||
render( | ||
<BrowserRouter> | ||
<App routes={routes} initialData={data} /> | ||
</BrowserRouter>, | ||
document.getElementById('root') | ||
); | ||
|
||
if (module.hot) { | ||
module.hot.accept(); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from 'react'; | ||
import Route from 'react-router-dom/Route'; | ||
function HttpStatus(props) { | ||
return ( | ||
<Route | ||
render={({ staticContext }) => { | ||
// we have to check if staticContext exists | ||
// because it will be undefined if rendered through a BrowserRouter | ||
if (staticContext) { | ||
staticContext.statusCode = props.statusCode; | ||
staticContext.url = props.url; | ||
} | ||
// @todo in Fiber, remove <div> | ||
return ( | ||
<div> | ||
{props.children} | ||
</div> | ||
); | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
export default HttpStatus; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import HttpStatus from './HttpStatus'; | ||
import React from 'react'; | ||
import Route from 'react-router-dom/Route'; | ||
|
||
function NotFound() { | ||
return ( | ||
<HttpStatus statusCode={404}> | ||
<div> | ||
<h1>404. Not Found.</h1> | ||
</div> | ||
</HttpStatus> | ||
); | ||
} | ||
|
||
export default NotFound; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import React from 'react'; | ||
|
||
// This is a Higher Order Component that abstracts duplicated data fetching | ||
// on the server and client. | ||
export default function SSR(Page) { | ||
class SSR extends React.Component { | ||
static getInitialData(ctx) { | ||
// Need to call the wrapped components getInitialData if it exists | ||
return Page.getInitialData | ||
? Page.getInitialData(ctx) | ||
: Promise.resolve(null); | ||
} | ||
|
||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
data: props.initialData, | ||
isLoading: false, | ||
}; | ||
this.ignoreLastFetch = false; | ||
} | ||
|
||
componentDidMount() { | ||
if (!this.state.data) { | ||
this.fetchData(); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
this.ignoreLastFetch = true; | ||
} | ||
|
||
fetchData = () => { | ||
// if this.state.data is null, that means that the we are on the client. | ||
// To get the data we need, we just call getInitialData again on mount. | ||
if (!this.ignoreLastFetch) { | ||
console.log('refetching'); | ||
this.setState({ isLoading: true }); | ||
this.constructor.getInitialData({ match: this.props.match }).then( | ||
data => { | ||
this.setState({ data, isLoading: false }); | ||
}, | ||
error => { | ||
this.setState(state => ({ | ||
data: { error }, | ||
isLoading: false, | ||
})); | ||
} | ||
); | ||
} | ||
}; | ||
|
||
render() { | ||
// Flatten out all the props. | ||
const { initialData, ...rest } = this.props; | ||
|
||
// if we wanted to create an app-wide error component, | ||
// we could also do that here using <HTTPStatus />. However, it is | ||
// more flexible to leave this up to the Routes themselves. | ||
// | ||
// if (rest.error && rest.error.code) { | ||
// <HttpStatus statusCode={rest.error.code || 500}> | ||
// {/* cool error screen based on status code */} | ||
// </HttpStatus> | ||
// } | ||
|
||
return ( | ||
<Page | ||
{...rest} | ||
refetch={this.fetchData} | ||
isLoading={this.state.isLoading} | ||
{...this.state.data} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
SSR.displayName = `SSR(${getDisplayName(Page)})`; | ||
return SSR; | ||
} | ||
|
||
// This make debugging easier. Components will show as SSR(MyComponent) in | ||
// react-dev-tools. | ||
function getDisplayName(WrappedComponent) { | ||
return WrappedComponent.displayName || WrappedComponent.name || 'Component'; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import app from './server'; | ||
import http from 'http'; | ||
|
||
const server = http.createServer(app); | ||
|
||
let currentApp = app; | ||
|
||
server.listen(process.env.PORT || 3000); | ||
|
||
if (module.hot) { | ||
console.log('✅ Server-side HMR Enabled!'); | ||
|
||
module.hot.accept('./server', () => { | ||
console.log('🔁 HMR Reloading `./server`...'); | ||
server.removeListener('request', currentApp); | ||
const newApp = require('./server').default; | ||
server.on('request', newApp); | ||
currentApp = newApp; | ||
}); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import About from './screens/About'; | ||
import Home from './screens/Home'; | ||
import Users from './screens/Users'; | ||
|
||
// This is a static route configuration. It should include all of your top level | ||
// routes, regardless of whether they are going to server render. In fact, you | ||
// can totally point multiple routes to the same component! This is great for | ||
// when you only need to server render a handful of routes and not your entire | ||
// application! | ||
const routes = [ | ||
{ | ||
path: '/', | ||
component: Home, | ||
name: 'Home', | ||
exact: true, | ||
}, | ||
{ | ||
path: '/about', | ||
component: About, | ||
name: 'About', | ||
exact: true, | ||
}, | ||
{ | ||
path: '/users', | ||
component: Users, | ||
name: 'Users', | ||
}, | ||
]; | ||
|
||
export default routes; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React from 'react'; | ||
import withSSR from '../components/withSSR'; | ||
|
||
class About extends React.Component { | ||
// This works similarly to Next.js's `getInitialProps` | ||
static getInitialData({ match, req, res }) { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
resolve({ | ||
article: ` | ||
This text is ALSO server rendered if and only if it's the initial render. | ||
`, | ||
currentRoute: match.pathname, | ||
}); | ||
}, 500); | ||
}); | ||
} | ||
|
||
render() { | ||
const { isLoading, article, error } = this.props; | ||
return ( | ||
<div> | ||
<h1>About</h1> | ||
{isLoading && <div>Loading...</div>} | ||
{error && | ||
<div> | ||
{JSON.stringify(error, null, 2)} | ||
</div>} | ||
{article && | ||
<div> | ||
{article} | ||
<div style={{ marginTop: '1rem', color: '#aaa' }}> | ||
{'>> '}Go to another route (Users) | ||
</div> | ||
</div>} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default withSSR(About); |
Oops, something went wrong.