Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Build Component Based Website

4.55/5 (6 votes)
31 Aug 2016MIT8 min read 16.2K  
Stateless functional components make building a multi-page website a breeze.

Introduction

SPA (Single Page Application) has been a modern, responsive and structured way building web applications, component based web development improves developer productivity and development efficiency. With the latest advancement in web platform, especially HTML5/Ajax/WebSocket/HTTP2, and popular JavaScript frameworks, web application development are in good hands. However, building a multi-page website is not the case.

Classic website still consists of multiple pages, primarily with static contents. Speed to load, easy navigation, page level SEO, UX tracking, etc. are essentials characteristics for multi-page websites. For a scaled content oriented website, consistent look and feel, sharable component among pages, and quick updates and deployment are also critical to web operations. Using SPA technologies to build a static content website has some significant challenges and downsides.

As the complexities of a web app grows, it's also required to separate out public facing website from authentication required functionalities, SPA approach is only applicable to a section of a large site. Especially when multiple teams, priorities, architectures and tech stacks are involved, breaking up SPA to multi-page website enables different sections to be architect, developed and updated in parallel and independently.

This article presents a component based technique for building static content oriented multi-page website with high compose-ability, reusability, maintainability and efficiency.

The project source code and a quick start guide can be found at https://github.com/modesty/react2html

Background

React2Html is an open source project aims to solve the challenges what SPA faces when building a static content based multi-page website. It utilizes React.js stateless functional component model to simplify development through compose-ability, reusability and share-ability. It prompts the "component" thinking -- everything in a page can be composed from lower level sharable components, encourages "separation of concerns", enables swift site-wide updates through a unique 'build time component model" that is easy to compose, customize and reuse.

At the heart of React2Html is a 'build' script to compile React components and pages into HTML markup, concatenate, uglify and bundle client side JavaScript (if any), concatenate, uglify and bundle SASS CSS modules, also creating page name based directories for each page, and of cause to copy assets to the right location in target directory, including images, icons, fonts and other media files.

With React2Html, the source code tree starts with components in JavaScript and JSX, CSS modules in SASS, each page is composed by those components in designated directory. After compilation (run the build script), all source code will be compiled and output as each page HTML file in its own directory, references the bundled client side JavaScript and CSS, together with copied over assets files.

For example, a typical source code tree structure for a multi-page website may look like:

react2html - sample source code structure

The build script will generate 'target' directory after compilation, makes it deployment-ready:

react2html - sample target directory structure after compile

Here below are some aspects worth noting:

  • Every JavaScript under src/scripts/client will be part of the bundled JavaScript, each client JavaScript file is developed its own ES6/ES2015 module, to be compiled by babel, uglified and bundled by webpack
  • All files in src root and src/images will be directly copied to target root and target/images directory
  • All JavaScript files under src/scripts/pages will be compiled to target/[page_name]/index.html. For example, src/scripts/pages/about is compiled as target/about/index.html. This directory structure enables clean urls for page navigation
  • All JavaScript files under src/scripts/model are build-time model only, it enables shareable src/scripts/component for different pages at build-time

Using the code

To get started, clone the GitHub repo first:

JavaScript
$ cd [dev_root]
$ git clone https://github.com/modesty/react2html.git
$ cd react2html

Install dependencies:

C++
$ npm i

Run the build

$ npm run build

Now you can examine the output in target directory.

For development and debug, simply run

$ npm start

It'll automatically clean up the target directly, re-compile the entire source tree, automatically launch the default browser on http://localhost:8181, and start to watch source file changes. Whenever there is a saved change to source code or asset files, it'll re-compile as needed then auto-refresh the browser.

Build Script Explained

All build scripts reside in src/tool directory, it does the heavy lifting for compilation, bundling and coping. It provides all essential functionalities out of box without changes. In case you need to further customize or extend it, here are some key customization points:

react2html build_scripts

  • base.config:
    • customize source and target directory name, scripts and SASS directory names and entry point.
    • customize asset file types
    • list of CSS and JavaScript libraries that loads from CDN at runtime
  • base.helper:
    • customize localhost dev server address, port, root serving directory, default file, etc.,
  • build.asset:
    • if you don't want the Apache .htaccess file, remove it from here
  • build.watcher:
    • customize which file and directory to watch for auto-re-compile and auto-refresh
  • webpack.config:
    • customize webpack modules (uglify, SASS, etc.), source map, output target, etc.

Add a New Page

Now we can see how easy and quick it is to add a new page to the sample website. Assuming the source code structure and build script stay as it is, we want to add a new 'contact' page that is accessible by http://localhost:8181.

First, create a new file named contact.js underneath src/scripts/pages directory. The new page can be easily constructed by composing components:

"use strict";<br />
import React from 'react';<br />
import Conf from '../../../tool/base.config';<br />
import Head from '../components/Head';<br />
import Header from '../components/Header';<br />
import Main from '../components/Main';<br />
import Footer from '../components/Footer';
let pkg = require("../../../package.json");<br />
const Contact = () => {<br />
   let pageTitle = `Contact Us`;<br />
   return (<br />
      <html className="no-js" lang="en"><br />
      <Head title={pageTitle} description={pkg.description} styles={Conf.styles} scripts={Conf.scripts} rel="../"/>
      <body className='container-fluid'><br />
      <Header title={pageTitle} rel="../"/><br />
      <Main><br />
         <h1>React2Html</h1><br />
         <p>email the author: <a href="mailto:modestyz@hotmail.com?subject=About%20CoeProject%20Article">modestyz@hotmail.com</a><br />
         </p><br />
      </Main><br />
      <Footer rel="../"/><br />
      </body><br />
      </html><br />
   );<br />
};
export default Contact;

It composes Head component for page meta tags, references library js/css from CDN and bundled js/css from target/styles path, it also utilizes Header, Main, Footer components to keep the common and consistent structure and look/feel of the site with few line of code, so that the page code can focus more on it's own specific content.

Second, build it:

$ npm start

That's it, the new page is at http://localhost:8181/contact

The compiled output is in target/contact/index.html,all uglified and minified to improve load performance.

When page header, footer, styles or CDN links changes, there is no change needed in contact page itself, the compilation will automatically pick up changes from corresponding components.

Update Model 

To add a link to our new contact page in navbar, which is part of Header component, just need to update its model in src/scripts/model/menu.js:

JavaScript
const MenuModel = [
   {name: 'Home', href: "."},
   {name: 'Portfolio', href: "#", children: [
      {name: "Products", href: "#"},
      {name: "Services", href: "#"}
   ]},
   {name: 'Team', href: "#", children: [
      {name: "Creative Team", href: "#"},
      {name: "Technical Team", href: "#"},
      {name: "Subject Domain Expert", href: "#"}
   ]},
   {name: 'Testimonial', href: "#", children: [
      {name: "Partners", href: "#"},
      {name: "Customers", href: "#"}
   ]},
   {name: 'Support', href: "#", children: [
      {name: "Contact Us", href: "#"},
      {name: "Schedule a Visit", href: "#"}
   ]},
   {name: 'About Us', href: "./about/"},
   {name: 'Contact Us', href: "./contact/"}
];

 Similarly, update footer model src/scripts/model/footer.js, to add a link to contact page in Footer component:

JavaScript
const FooterModel = {
   siteLinks: [
      {name: "About", href: "./about"},
      {name: "Legal", href: "#"},
      {name: "Privacy", href: "#"},
      {name: "Contact", href: "./contact"}
   ],
   socials:[
      {id: "icon-facebook", href: "http://facebook.com"},
      {id: "icon-twitter", href: "http://twitter.com"},
      {id: "icon-youtube", href: "http://youtube.com"},
      {id: "icon-linkedin", href: "http://linkedin.com"},
      {id: "icon-dribbble", href: "http://dribbble.com"},
      {id: "icon-rss", href: "http://rss.com"}
   ],
   copyRight: "Modesty Zhang © 2015 - 2016",
   addressLine1: "7330 Clairemont Mesa Blvd,",
   addressLine2: "San Diego, CA 92111",
   phone: "+1-111-222-3333",
   email: "modestyz@hotmail.com"
};

Stateless Functional Components

React.js introduced stateless functional components in v0.14, it's also at the core of react2html to create multi-page website using components. Stateless and functional means all the components in react2html have no internal states, the output markup will be the same if the passed in properties are the same.

In reac2html case, all components' properties are passed in via model. For example, given the HeaderModel above, here is the code for Header component:

"use strict";
import React from 'react';
import Menu from './Menu';
import processRelPath from '../model/header';
const Header = ({ title, rel }) => {<br />
   let dataModel = processRelPath(rel);<br />
   let {homeLink, bannerLink} = dataModel;
   return (<br />
      <header className='header'><br />
         <div className="row"><br />
            <div className="col-md-3 col-xs-12 hidden-xs"><br />
               <div className="logo"><br />
                  <a href={homeLink.href} title={homeLink.name}><br />
                     <img src={homeLink.img} alt={title} /><br />
                  </a><br />
               </div><br />
            </div><br />
            <div className="col-md-6 col-xs-12"><br />
               <div className="banner-area"><br />
                  <a href={bannerLink.href}><br />
                     <img src={bannerLink.img} alt={bannerLink.name} className="img-responsive" /><br />
                  </a><br />
               </div><br />
            </div><br />
            <div className="col-md-3 hidden-sm hidden-xs"><br />
               <div className="soc-area"><br />
                  <div className="icons-social"><br />
                     {dataModel.socials.map( s => <a href={s.href} className={s.id} key={s.id}></a>)}<br />
                  </div><br />
               </div><br />
            </div><br />
         </div><br />
         <Menu rel={rel}></Menu><br />
      </header><br />
   );<br />
};
export default Header;
The Footer component code is very similar, uses the simple function syntax, has no internal state, behave like a pure component, or dumb component, at build time.

The stateless functional component patterns encourages constructing web pages through different level of compositions of these simple components, enables developers to express data-driven UI using JavaScript and JSX. In an abstract sense, it utilizes React's idea to enable creating react-agnostic components and its composition to create more complex multi-page websites.

Depends on the nature and complexities of the website, model can be retrieved and constructed by API / Database / CMS in build time or run time. React components and other JavaScript libraries and frameworks can also be employed at runtime to response to data store changes too. The sample code just focus on static website with static data models.

Although the component code and compilation output are React agnostic, current implementation does leverage React's 'isomorphic' feature, ReactDOMServer.renderToStaticMarkup is the key support that makes react2html work efficiently at build tme.

Wrap it up

If you are building a static content multi-page website with frequent updates, and facing challenges like SEO, initial load time, browser history and navigation, clean url, analytics and tracking, code partitioning and duplication, etc., react2html can help. It utilizes stateless functional components in build time, enables data-driven multi-page website developments through reusability and share-ability.

More details and all source code can be found at https://github.com/modesty/react2html. Contribution and pull requests are welcomed.

License

This article, along with any associated source code and files, is licensed under The MIT License