You May Not Need Next.js
This is an example of how to mimic Next.js's route-level data fetching using Razzle, React Router 4, a static route config, and nifty Higher Order Component. This technique originially came from @ryanflorence's gist that can be found here.
How to use
Download the example or clone the whole project:
curl https://codeload.github.com/jaredpalmer/razzle/tar.gz/master | tar -xz --strip=2 razzle-master/examples/you-may-not-need-next.jscd you-may-not-need-next.js
Install it and run:
yarn installyarn start
Walkthrough
To fetch data isomorphically add a static getInitialData
to your component.
Component static { // `req`, and `res` only. We have access to React Router's `match` here. // and pass thru an instance of axios for data fetching. Bonus idea: // You can also make the axios instance setup for isomorphic authenticated // data fetching by setting default baseURL and headers. return axios } state = // this initialData would come from window.DATA if it's the first load. data: thispropsinitialData || null error: null ; { if !thisstatedata // 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. thisconstructor ; } { // Everything is injected into this.props.data // UNLIKE Next.js, ONLY first page-load render is blocked. So we need // to handle a loading state (when this.props.data === null). This is // awesome becuase it gives users immediate feedack instead of an empty // screen. return <div> thisstatedata === null ? <div>Loading...</div> : <div>thisstatedataname</div> </div> }
We also need to add our new component to our static route config in common/routes.js
.
// common/routes.js... const routes = path: '/users/:id' component: MyPage exact: true ...
To stay DRY we can actually extract the componentDidMount
stuff into a Higher Order Component.
;; // This is a Higher Order Component that abstracts duplicated data fetching// on the server and client. { Component static { // Need to call the wrapped components getInitialData if it exists return PagegetInitialData ? Page : Promise; } state = data: thispropsinitialData || null error: null ; { if !thisstatedata // 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. thisconstructor ; } { // Unlike Next, which flatly returns all props returned by `getInitialData`, // Our function places them in data or error. If you'd rather flatten // your props you can use something like recompose to do so. return <Page ...thisprops data=thisstatedata error=thisstateerror /> ; } SSRdisplayName = `SSR()`; return SSR;} // This make debugging easier. Components will show as SSR(MyComponent) in// react-dev-tools. { return WrappedComponentdisplayName || WrappedComponentname || 'Component';}
Now our pages can look like this:
Component static { return axios } { return <div> thispropsdata === null ? <div>Loading...</div> : <div>thispropsdataname</div> </div> }
Sometimes you may not want to server render.
Imagine you have some routes like /settings/profile
, /settings/billing
.
These don't need to be server rendered. React Router 4 still works exactly how
you want it too. Remember...routes are just components!
Here's how we could write our settings pages...
Component { ... } { return <div> <Switch> <Route path="/settings/general" render= ... /> <Route path="/settings/profile" render= ... /> </Switch> </div> }
We just need to tell our server that we should direct requests to
/settings/profile
AND settings/general
to the SAME component in our static
route config.
// common/routes.js... const routes = path: '/users/:id' component: MyPage exact: true path: '/settings/general' component: Settings exact: true path: '/settings/profile' component: Settings exact: true ...