The "nearly invisible" server-rendering framework for React applications
Rogue streamlines the process of creating server-rendered React applications.
We call Rogue a nearly invisible library, because it doesn't require a special /pages
directory (like Nextjs) or a separate routes.js
file (like Afterjs); all you need, is the App.js
component you'd usually have. This means that, staying true to React's values, you can organize your code however you like.
We're able to give you back control of your application, because we leverage React Router (for dynamic routing) and Apollo Graphql (for querying data), which together dispense with the need to split your server-rendered routes into distinct entry points. With these libraries, everything already happens on a per component basis, so we just handle the server-rendering setup for you.
First, install the package:
npm install @roguejs/app
In your server.js
initialize your Rogue app by passing it your root App component and script to your client bundle:
import rogue from '@roguejs/app/server'
import App from './App'
const app = rouge(App, process.env.BUNDLE_URL)
app.listen(4000)
In your client.js
hydrate your Rogue app:
import hydrate from '@roguejs/app/client'
import App from './App'
hydrate(App)
And that's it! With just a few lines of code, you have a server rendered React application.
To get you're application running, we recommend an SSR build tool such as razzle. Check out the with-razzle example to see how to get started.
Rogue can also be used with react-native-web
. If you're interested in this, check out the with-react-native example.
rogue(App: React.Component, bundleUrl: string, options: Object)
Accepts the following options:
renderToString(app, ctx)
: a custom metho for rendering app node to static markup.headTags
: array of head tags to include in html document.bodyTags
: array of body tags to include in html document.
Has the following methods:
preuse(fn)
:Function
to add a middleware before the render middleware.use(fn)
:Function
to add a middleware after the render middleware.render(req, res)
:Function
to run the rogue middleware stack against Node'sreq
andres
objects.listen(port, callback)
:Function
to start the app listening for requests. Alias to Nodejsserver.listen
.
Any logic you'd like to handle upon server rendering can be done inside a component's static getInitialProps
method (we kept the same property name as Nextjs
to pay homage to the grandaddy of React SSR frameworks).
It's important to note that Rogue only calls getInitialProps
inside your App.js
component (and not inside pages
like Nextjs).
The reason for that is that Rogue assumes you're using Apollo Graphql and React Router 4. So that elimates the two primary use-cases for getInitialProps
inside pages: querying data and handling redirects.
Nonetheless, getInitialProps
is still useful for bootstrapping your application with server specific logic. You can use it to configure SSR support for external libraries (@roguejs/hocs
, or, another common scenario, is refreshing an authenticated user. Here's how that might look like:
// note: this example uses our apollo and redux hocs
export default class App extends React.Component {
static async getInitialProps({ req, res }) {
const token = getToken(ctx.req)
if (!ctx.store.getState().session.user) { // refresh session
try {
const res = await ctx.apollo.query({ query: authUserQuery })
ctx.store.dispatch(setSession({ user: res.data.authUser }))
} catch (err) { // invalid or expired token
ctx.store.dispatch(removeSession())
}
}
}
}
If you return any value from getInitialProps
, make sure that it is a plain Object
, as it will be serialized when server rendering.
This data will then be passed to the component exported from your App.js
file.
req
: (server-only) A nodejs Request objectres
: (server-only) A nodejs Response objectapp
: (server-only) An object, with properties to configure SSRroutable
: A function accpets a Component and returns it wrapped in router environmentheadTags
: An array of head tags to include in html document,bodyTags
: An array of body tags to include in html document,
isServer
: A boolean to indicate whether current environment is serverfullPath
: The full resolved URL including query and hash.path
: A string that equals the path of the current route.query
: An object that contains key/value pairs of the search string.
There are two ways to manage document tags: application side or server side.
To manage document tags within your application, Rogue has automatic support for react-helmet. Check out their documentation for usage, but here's a basic example:
import { Helmet } from 'react-helmet'
export default () => (
<React.Fragment>
<Helmet>
<title>My Rogue App!</title>
</Helmet>
<App />
</React.Fragment>
)
To manage document tags for server-logic, you can use Rogue's ctx
object. Here's an example of how you might use Rogue's API to setup a CSS-in-JS library such as styled-components:
const app = rouge(App, process.env.BUNDLE_URL, {
renderToString(app, ctx) {
const markup = ReactDOM.renderToString(app)
const sheet = new ServerStyleSheet()
sheet.collectStyles(app)
ctx.app.headTags.push(sheet.getStyleTags())
return markup
}
})
You can use Rogue with your own custom server. Simply pass rogue.render
to your app's middleware:
import Rogue from '@roguejs/app/server'
import express from 'express'
import App from './app/App'
const rogue = new Rogue(App)
const app = express()
app.use(rogue.render)
export default app
There are two Rogue packages:
@roguejs/app
, holds the core modules for the Rogue library. You can use this package to streamline your SSR experience independent of any build setup.@roguejs/hocs
, holds higher order components that come preconfigured with SSR support for Rogue. You can use this package to enhance your application without uncessary SSR boilerplate.
- Alid Castano (@alidcastano)