Improve Page Speed Using Isomorphic JavaScript With React/Redux At Ameba

We have made some big changes to Ameblo mobile pages, the most popular blog in Japan. We switched our system from Java to node.js with react/redux in September this year. We know loading content instantly is the most important point for UX, so the main goal of the changes is speed up.

We needed to update our product’s UI/UX and architecture. We think making good UI/UX and using modern ecosystem is really important for creating great product.

We use these the technologies and the techniques listed below.

SSR and SPA

We mixed Server Side Rendering (SSR) for the first renderer with Single Page App (SPA) for runtime rendering. Both are important for blogs, as users often browse several pages after landing on an article.

SSR is good system for the first renderer and SEO. Minimum amount of content included in HTML are needed to display above the fold.

SPA’s speed is amazing. Only small amount of data from API are needed to display the pages. It makes big advantage for UX.

All pages were rendered as SSR in the previous system. We use SPA as the runtime renderer for UX in current system.

Lazy Load

We use lazy load to render the first page faster. Only main content such as the article is included in the HTML, and the sub content is loaded as users scroll. We can reduce the HTML content size and the unused requests using this technique.

HTML size tended to too big for the first renderer, as all contents are included.

React and Redux

React/Redux is a great tool for web developers. We can create a lot of logics and components as pure functions with React/Redux. It makes developers managing app state and keeping logics small easier.

First, Redux action creators return plain object as Redux actions. Next, Redux reducers return new objects as new Redux states merged with previous states. Last, React components receive the states as props, and return elements.

() => action, (previousState, action) => newState, (props) => el

The flow is one way, and all of them are managed as pure functions.

Isomorphic Web App

Ameblo app ver. 2016 is written in JavaScript. Almost all codes work for both client side (browsers) and server side (node.js) by the same flow.

The app works by the same flow on both clients and servers.
Ameblo 2016 app is made by JavaScript.

Atomic Design

We first manage components using containers and components directories recommended by Redux (Presentational and Container Components). However, we finally chose Atomic design. It makes each components’ role clearer.

  • Atoms (Minimum, not having state, e.g. Icon, Button)
  • Molecules (To be re-use, not having state, e.g. List, User thumbnail)
  • Organisms (Big parts, having state, fetching data, e.g. Navi, Article)
  • Templates (Templates of each pages, listing organism modules, e.g. EntryList, Entry)
  • Pages (only one page due to SPA)

ESLint and stylelint

We use ESLint and stylelint for all code consistency. Our rules are extended from eslint-config-airbnb and stylelint-config-standard. Developers aren’t able to pass CI test, if their code has errors. The rules are strict, so it can be a little difficult for new members. But we think using tool is better than reviewing and point out mistakes by hand.

We can often see CI errors by new members.

Big Picture

This is a big picture of our new system.

When developer pushes their code to GHE, testing and building automatically start on CircleCI. After finishing that, CircleCI deploys static files to CDN and starts web servers.

Our web servers are in Docker containers. Docker brings many benefits to node.js app. Once we make docker image, the image can work anywhere. We can deploy or revert anytime, and updating the node.js version become easier.

The app code is generated by webpack and Babel. We use webpack-isomorphic-tools, babel-preset-es2015, babel-preset-react, transform-decorators-legacy etc. This is an example component code. Decorator is useful for React components.

// components/organisms/SpProfile.js

import React from 'react';
import { connect } from 'react-redux';
import { routerHooks } from 'react-router-hook';

import { fetchBloggerRequest } from '../../../actions/bloggerAction';

const defer = async ({ dispatch }) => {
await dispatch(fetchBloggerRequest());
};

const mapStateToProps = (state, owndProps) => {
const amebaId = owndProps.params.amebaId;
const bloggerMap = state.bloggerMap;
const blogger = bloggerMap[amebaId];
const nickName = blogger.nickName;

return {
nickName,
};
};

@connect(mapStateToProps)
@routerHooks({ defer })
export class SpProfileInfo extends React.Component {
static propTypes = {
nickName: React.PropTypes.string.isRequired,
};

render() {
return (
<div>{this.props.nickName}</div>
);
}
}

Update UI/UX

We updated some UI for better UX in 2016.

The new UI fixes content view sizes. Late loading content can cause miss tap. I think miss tap is the worst UX.

The worst UX is miss tap.

We have started to provide content-first design.

Accessibility

We have started to pay attention to accessibility since this new release. HTML is accessible enough, so we try to markup correctly. For example, adding alt to <img>, and markup as <a> or <button> for clickable elements. To check this automatically, we use jsx-a11y plugin.

At the same time, we have begun to try to markup for voice readers. We use WAI-ARIA, like aria-hidden, aria-label, role attribute etc.

Results

These stats are from SpeedCurve and Google Analytics comparing August with September in 2016.

  • Critical Blocking Resources (Improved 75%)
  • Content Requests (Improved 58.04%)
  • Rendering (Improved 44.68%)
  • Page Load Time (Improved 40.5%)
  • Pageviews (Improved 57.15%, actual around 20%, some bloggers released breaking news in September)
  • Page / Session (Improved 35.54%)
  • Bounce Rate (Improved 44.44%)

:sushi::beer:!