Ghost Themes
SimpleChat.Support - Open Source Live Chat App
0%
Server-Side Rendering (SSR) in Meteor
words - read.

Server-Side Rendering (SSR) in Meteor

Meteor platform is still alive! Of course it is, and it is constantly improved. This isn’t as dynamic process as in the past, but I hope Meteor will be a complete and flexible platform some day. Maybe it needed to slow down a little bit. For me, the most interesting improvement lately is Server Side Rendering. I’ll try to tell you why, and especially why I like it in Meteor.

What we will cover here:

  1. Why is Server Side Rendering (SSR) good to have, especially in Meteor?
  2. How it is implemented in Meteor?
  3. Real example in ready-to-use boilerplate.

Why is Server Side Rendering (SSR) good to have, especially in Meteor?

Some time ago I wrote an article about why I keep coming back to Meteor. I wrote there that I use Meteor in projects that aren't that demanding because of some of its limitations. Anyways, it is still the most complete platform and it’s very simple to start with. Of course I still think this is the best platform for fast prototyping and not so complicated projects, but if it improves, I am sure it will be also suitable for big apps (just my opinion).
It has ultra simple deployment options and build system works automatically. This is why there are so much devs who like it. And now we have SSR on board with a couple of other nice features like dynamic imports (topic for separate article).

Server Side Rendering is probably the most sought feature in website-like projects. So, projects which aren’t very big. Projects which are half apps and half websites. Why is it nice? Because you can prepare a website which is accessible for all web crawlers out there. Your SEO will shine with that. Of course Google bots should index your website anyway, but what with Facebook or Twitter bots, what with sharing materials on social services? I always had problems with that, even when building simple projects.

Having SSR on board with simple configuration is what I like in all frameworks. Of course there will be many situations when you will choose not to use SSR because you have much more benefits from not using it, for example separated front-end and backend (different servers) or maybe you just don’t need it in the app, because you just want to build app which is used internally or all content and functionalities are just available for private access, only for logged in users, so you just don’t care. There are always drawbacks and you need to decide.

You’ll probably think, so how did it all work without that before? There are couple of options. SSR isn’t something which is a significant improvement. You can implement some workarounds which are also very good. You can use services like Prerender.io. You can use your own server which will generate static websites and serve them for web crawlers only. I guess this is just a matter of choosing what is most valuable in the app you’re building.

So, why do I like SSR especially in Meteor? Because this is the next missing puzzle in a very simple to start platform. Without any big configuration and other preparations. You can just focus on coding. So, for me it is much better to use it with Meteor because I can get not only SSR, but the whole stack up and running very fast. Of course there are plenty of options out there, other frameworks etc., but it is always better to have such features in a platform you know.

How it is implemented in Meteor?

This is the most interesting part of this article, so without further ado, let’s see what it looks like.

First of all, you need a new package called server-render, which needs to be added to your Meteor app. You can do this by writing in the terminal: meteor add server-render. Unlike dynamic-import package, this one isn’t added when creating a new Meteor app. I think this is good. You’ll be able to decide and add it if only needed.

After you add the package, you need to be sure that your components will be accessible on both the server and client side. I use React so this is very simple to achieve. I am not sure how it looks with for example Angular. Later you’ll see how I’ve got it structured in my simple boilerplate. I haven’t tested it yet, but the package should work with all modern front-end frameworks which can be integrated with Meteor. For now, let’s see what the API looks like.

So the server-render package exposes onPageLoad method and sink object with useful methods which allow you to modify the HTML that will be served from the server. Basically what we need to do on the server-side is to parse our main App component to the string using built-in React methods (so, the standard way of doing SSR with React) and then we can use sink.renderIntoElementById to put the whole HTML in proper place in the body. onPageLoad will fire on every request so current source code of the page should be updated. You can use other methods from sink. You can append some elements to the head tag. For example, you can use react-helmet on the server, which you should do. I strongly recommend you that. This was always problematic, especially when you want to share your content on social services like Twitter or Facebook. For example, you can inject Open Graph tags and other meta tags so Facebook or Twitter bots will see them properly. This is very useful.

I don’t want to copy and paste the whole documentation here, you’ll find more in the Readme file of the package linked above. Instead of just documentation, I want to jump to the real example, which is my simple boilerplate. Actually, this is the old boilerplate that was recently updated, so there is probably some mess in it. But this is not the case for now. Let’s see how SSR looks like there.

Real example in ready-to-use boilerplate.

Lately I’ve updated my Meteor boilerplate to be ready for SSR. It also uses Redux, so it will be a good opportunity to show how to use Redux and SSR at once.

This is a pretty standard boilerplate based on Meteor Guide, so the folders structure should be simple for every Meteor developer. I have client app initialization and server-side app initialization. I also share React routes and Redux actions and reducers between the server and client.

Let’s see what we have on the client side first.

import React from 'react';  
import ReactDOM from 'react-dom';  
import { BrowserRouter, Switch } from 'react-router-dom';  
import { onPageLoad } from 'meteor/server-render';  
import { Provider } from 'react-redux';  
import thunk from 'redux-thunk';  
import { createStore, applyMiddleware } from 'redux';  
import routes from '../both/routes';  
import mainReducer from '../../api/redux/reducers';

const preloadedState = window.__PRELOADED_STATE__; // eslint-disable-line

delete window.__PRELOADED_STATE__; // eslint-disable-line

const store = createStore(mainReducer, preloadedState, applyMiddleware(thunk));

const App = () => (  
  <Provider store={store}>
    <BrowserRouter>
      <Switch>
        {routes}
      </Switch>
    </BrowserRouter>
  </Provider>
);

onPageLoad(() => {  
  ReactDOM.render(<App />, document.getElementById('app'));
});

As you can see, I use Redux, so when using SSR I can get the initial state for the store on the server and pass it in the static generated HTML. This is recommended way. Here on the client side, I just get the initial state from special object injected in the body. You’ll see how it’s done later. Then I create the Redux store with initial data and prepare standard main App component with routing. I use React Router 4 here. This is all what you need on the client side for now.

Let’s see what we have on the server-side.

import React from 'react';  
import { renderToString } from 'react-dom/server';  
import { onPageLoad } from 'meteor/server-render';  
import { StaticRouter } from 'react-router';  
import { Provider } from 'react-redux';  
import { createStore, applyMiddleware } from 'redux';  
import thunk from 'redux-thunk';  
import { object } from 'prop-types';  
import { Helmet } from 'react-helmet';  
import mainReducer from '../../api/redux/reducers';  
import routes from '../both/routes';  
import { todosGetAll } from '../../api/todos/methods';

onPageLoad((sink) => {  
  const context = {};
  const initial = todosGetAll.call({});

  const store = createStore(mainReducer, { todos: initial }, applyMiddleware(thunk));

  const App = props => (
    <Provider store={store}>
      <StaticRouter location={props.location} context={context}>
        {routes}
      </StaticRouter>
    </Provider>
  );

  App.propTypes = {
    location: object.isRequired,
  };

  const preloadedState = store.getState();

  sink.renderIntoElementById('app', renderToString(<App location={sink.request.url} />));

  const helmet = Helmet.renderStatic();
  sink.appendToHead(helmet.meta.toString());
  sink.appendToHead(helmet.title.toString());

  sink.appendToBody(`
    <script>
      window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
    </script>
  `);
});

Here is where all magic happens. We need to wrap all the code in onPageLoad method just to be able to rerun it and generate new static HTML on every page request. Configuration is similar. Here we can create the initial state for our Redux store. Then we can create the store and prepare main component. What is different here? We use static router configuration here because on the server there isn’t browser history, etc. Another thing which needs to be done is putting the initial state into our static HTML code. We do this using sink.appendToBody method from server-render package. All main code is generated using React’s renderToString and then it is injected into the app div element using sink.renderIntoElementById.

What else can we do here? Of course we can inject some meta tags, like Open Graph tags or just title and description for every page. We can do this using the react-helmet library. We use the Helmet.renderStatic method which returns data that then can be appended to the tag of our static document. Of course this will be different data for every page. We use the sink.appendToHead method to be able to do that.

That’s basically it. Having this API, we are able to configure SSR in a very simple way. This is another part of Meteor which makes this platform very nice to use for every developer. Let me know what you think. Let me know how you use it in your projects.

To sum up this short article.

I just wanted to do some research on the newest SSR options in Meteor and I must say that I like what I saw. I think Meteor is going in the right direction. It is faster and uses more and more of the newest approaches to the build processes. Apollo Integration also looks really good. The only things that are missing for me are: a more configurable and flexible build system and a more extendable accounts system decoupled from the Meteor and Live Data. Or at least more documentation on that. All other stuff looks very good.

Have a cool project in mind? -> psd2Meteor - Meteor apps development!

Comments

comments powered by Disqus