A service for server-side rendering your JavaScript views
First and foremost, server-side rendering is a better user experience compared to just client-side rendering. The user gets the content faster, the webpage is more accessible when JS fails or is disabled, and search engines have an easier time indexing it.
Secondly, it provides a better developer experience. Writing the same markup twice both on the server in your preferred templating library and in JavaScript can be tedious and hard to maintain. Hypernova lets you write all of your view code in a single place without having to sacrifice the user‘s experience.
hypernova-ruby
or hypernova-node
. It is the client which gives your application the superpower of querying Hypernova and understanding how to fallback to client-rendering in case there is a failure.First you‘ll need to install a few packages: the server, the browser component, and the client. For development purposes it is recommended to install either alongside the code you wish to server-render or in the same application.
From here on out we‘ll assume you‘re using hypernova-ruby
and React
with hypernova-react
.
npm install hypernova --save
This package contains both the server and the client.
Next, lets configure the development server. To keep things simple we can put the configuration in your root folder, it can be named something like hypernova.js
.
var hypernova = require('hypernova/server');
hypernova({
devMode: true,
getComponent(name) {
if (name === 'MyComponent.js') {
return require('./app/assets/javascripts/MyComponent.js');
}
return null;
},
port: 3030,
});
Only the getComponent
function is required for Hypernova. All other configuration options are optional. Notes on getComponent
can be found below.
We can run this server by starting it up with node.
node hypernova.js
If all goes well you should see a message that says "Connected". If there is an issue, a stack trace should appear in stderr
.
If your server code is written in a language other than Ruby, then you can build your own client for Hypernova. A spec exists and details on how clients should function as well as fall-back in case of failure.
bundle install hypernova
Now lets add support on the Rails side for Hypernova. First, we‘ll need to create an initializer.
config/initializers/hypernova_initializer.rb
Hypernova.configure do |config|
config.host = "localhost"
config.port = 3030 # The port where the node service is listening
end
In your controller, you‘ll need an :around_filter
so you can opt into Hypernova rendering of view partials.
class SampleController < ApplicationController
around_filter :hypernova_render_support
end
And then in your view we render_react_component
.
<%= render_react_component('MyComponent.js', :name => 'Hypernova The Renderer') %>
Finally, lets set up MyComponent.js
to be server-rendered. We will be using React to render.
const React = require('react');
const renderReact = require('hypernova-react').renderReact;
function MyComponent(props) {
return <div>Hello, {props.name}!</div>;
}
module.exports = renderReact('MyComponent.js', MyComponent);
Visit the page and you should see your React component has been server-rendered. If you‘d like to confirm, you can view the source of the page and look for data-hypernova-key
. If you see a div
filled with HTML then your component was server-rendered, if the div
is empty then there was a problem and your component was client-rendered as a fall-back strategy.
If the div
was empty, you can check stderr
where you‘re running the node service.
The developer plugin for hypernova-ruby
is useful for debugging issues with Hypernova and why it falls back to client-rendering. It‘ll display a warning plus a stack trace on the page whenever a component fails to render server-side.
You can install the developer plugin in examples/simple/config/environments/development.rb
require 'hypernova'
require 'hypernova/plugins/development_mode_plugin'
Hypernova.add_plugin!(DevelopmentModePlugin.new)
You can also check the output of the server. The server outputs to stdout
and stderr
so if there is an error, check the process where you ran node hypernova.js
and you should see the error.
The recommended approach is running two separate servers, one that contains your server code and another that contains the Hypernova service. You‘ll need to deploy the JavaScript code to the server that contains the Hypernova service as well.
Depending on how you have getComponent
configured, you might need to restart your Hypernova service on every deploy. If getComponent
caches any code then a restart is paramount so that Hypernova receives the new changes. Caching is recommended because it helps speed up the service.
Isn‘t sending an HTTP request slow?
There isn‘t a lot of overhead or latency, especially if you keep the servers in close proximity to each other. It‘s as fast as compiling many ERB templates and gives you the benefit of unifying your view code.
Why not an in-memory JS VM?
This is a valid option. If you‘re looking for a siloed experience where the JS service is kept separate, then Hypernova is right for you. This approach also lends itself better to environments that don‘t already have a JS VM available.
What if the server blows up?
If something bad happens while Hypernova is attempting to server-render your components it‘ll default to failure mode where your page will be client-rendered instead. While this is a comfortable safety net, the goal is to server-render every request.
These are pitfalls of server-rendering JavaScript code and are not specific to Hypernova.
You‘ll want to do any DOM-related manipulations in componentDidMount
. componentDidMount
runs
on the browser but not the server, which means it’s safe to put DOM logic in there.
Putting logic outside of the component, in the constructor, or in componentWillMount
will
cause the code to fail since the DOM isn‘t present on the server.
It is recommended that you run your code in a VM sandbox so that requests get a fresh new
JavaScript environment. In the event that you decide not to use a VM, you should be aware that
singleton patterns and globals run the risk of leaking memory and/or leaking data
between requests. If you use createGetComponent
you’ll get VM by default.