Want to be More Productive? Checkout my Open Source Productivity Application

Building a Static Website Using React JS: Part #2 Creating and Hosting the Website

Let's build our entire website and host it., This is going to be simple tutorial.

You can checkout Part #1 of this tutorial here Building a Static Website Using React JS: Part #1 Project Setup and Website UI

Before We Get Started

I will be adding different pages, components, css styles in this tutorial, You're free to use your own Style, layouts and other components as needed

How you design your UI and how you structure everything is up to you, You have full freedom to use your design skills and make a killer website.

And if you want, You can use the Structure and CSS i will be using in this tutorial., Just remember you don't have to do it the way i am doing, You will still be able to add Meta tags and generate the HTML pages irrespective of your UI design and structure.

Step#1: Creating our Layout, Header and Common Components

Following our previous tutorial, We will start building our website now.

Although every page of our website can have different structure, content and meta information, It will be best if we follow same Layout for all the pages.

Let's create a Layout component which we can use for all of our pages. This will make all the pages of website follow same UI structure.

Create a file named src/layouts/Default.js and place this code in it.

'use strict'; import React from 'react'; import Header from 'app/components/Header'; const DefaultLayout = (props) => { return ( <div className="website--layout"> <Header /> <div className="page-content"> <div className="container"> { props.children } </div> </div> </div> ); } export default DefaultLayout;

This is a simple Component, All this does it add few classes and render the sub/children components within those div.

You can see we have imported import Header from 'app/components/Header'; But the Header component doesn't exist yet. So let's add the Header component.

Create a file named src/components/Header.js and add this code in it

'use strict'; import React from 'react'; import { Link } from 'react-router'; const Header = (props) => { return ( <header className="main"> <div className="container"> <div className="logo"> <Link to="/">Productivity Application</Link> </div> <nav> <Link to="/" activeClassName="active">Home</Link> <span className="sep"></span> <Link to="/features" activeClassName="active">Features</Link> <span className="sep"></span> <Link to="/about" activeClassName="active">About</Link> <span className="sep"></span> <Link to="/contact-us" activeClassName="active">Contact Us</Link> </nav> </div> </header> ); } export default Header;

All this Header component does is setup our Logo (in this case text logo) and Links to different pages in our website.

As you can see we have four different Links here Home, Features, About and Contact Us, These are the pages we are going to create and have in our website. We will also have a 404/Page Not Found page.

Don't worry, In later tutorials we will come back to this add our Blog section along with few more pages.

Let's also create few common components which we will use in few of our Pages., Create a file named src/components/UI.js and add this code in it

'use strict'; import React from 'react'; import { Row, Col } from 'antd'; const Heading = (props) => { return ( <Row> <Col span={14} offset={5}> <div className="heading"> { props.title } { props.subtitle && <div className="subtitle">{ props.subtitle }</div> } </div> </Col> </Row> ); } const URL = (props) => { return ( <a href={props.to} target="_blank" rel="nofollow">{ props.title ? props.title : props.to }</a> ) } export { Heading, URL, };

Step#2: Adding All The Pages

Let's create and all the pages we need for our website.

Create a file named src/content/Home.js and this code in it

'use strict'; import React from 'react'; import DefaultLayout from 'app/layouts/Default'; import { Heading } from 'app/components/UI'; import { Row, Col, Carousel } from 'antd'; const Home = (props) => { return ( <DefaultLayout> <Heading title="Hey You, Yes You!, Want to be More Productive? Have lists of things you care about? Love simple and sexy UI?" /> <Col span={24} className="component--slider"> <Carousel autoplay autoplaySpeed={5000}> <div> <div className="image"> <img src="/images/slider/1.png" /> </div> <div className="title">This is a screenshot of the Board view page</div> </div> <div> <div className="image"> <img src="/images/slider/2.png" /> </div> <div className="title">This is a screenshot of The Login page in Chinese Language.</div> </div> </Carousel> </Col> </DefaultLayout> ); } export default Home;

This is a simple component that includes other components, Carousel, Row, Col components are provided by the ant-design UI library we installed, This make UI design process lot easy.

I have also added links to two different images, images/slider/1.png and images/slider/2.png. I will add these images/screenshots at the specified location now.

Let's continue, Create a file named src/content/Features.js and place this code in it

'use strict'; import React from 'react'; import DefaultLayout from 'app/layouts/Default'; import { Heading } from 'app/components/UI'; import { Row, Col, Icon } from 'antd'; const Features = (props) => { const APPLICATION_FEATURES = [ { status: true, title: 'Static Application', description: 'You can host the app on any Static Host/CDN instead of a server', }, { status: true, title: 'Boards', description: 'Boards are the gateway to your lists, You can have as many boards as you want', }, { status: true, title: 'Lists', description: 'Each list can easily be re-arranged and updated, You can add multiple cards to a list', }, { status: true, title: 'Cards', description: 'Cards are the meat of this app, you can add as many cards as you like, re-arrange them, drag them from one list to another, etc', }, { status: true, title: 'Todo List', description: 'Each card has Todo List tab, There you can add your todo list items, update them, mark them as completed and so on.', }, { status: true, title: 'Card Meta', description: 'Each card has meta section where you can specify Duedate, Link, Image and the appropriate icons will appear below card in the list view., If image URL is specified, Image will appear above the card title.', }, { status: true, title: 'Custom Background', description: 'Each board, list and card can have different Background color, Boards can have background images as well. To change the background color of board just edit the board by clicking the Edit icon below the header and there you can update board details along with background color.', }, { status: true, title: 'Settings', description: 'You can update your details, password and preferred language in the settings page', }, { status: true, title: 'Public Boards', description: 'Now you can make boards public, Public boards are accessible to all the users with the board URL., By default all boards are private.', }, { status: true, title: 'Code Splitting', description: "Split the code into different files and only load those files when necessary., Enable tree shaking so we only include the code we're actually using in the app.", }, { status: true, title: 'Lists Spacing', description: 'Now you can add spaces between lists, You can add space before and after a list. (might be useful to some of you)', }, { status: true, title: 'Customizations', description: 'Now you have more control over specifying background colors, you can either select it using colorpicker or enter it manually, it can be Color Names, HEX, RGB or RGBA.', }, { status: true, title: 'Multiple Languages', description: 'Added support for multiple languages, Current translation of Chinese langugae is done using Google Translate.', }, { status: true, title: 'Card Positioning', description: 'Now you can top and bottom margin to any card, Giving your more flexibility and control over the UI.', }, { status: true, title: 'Loading Indicator', description: 'Since the project makes use of Webpack code splitting, Sometimes it felt like clicks were unresponsive, Now you can see loading message whenever new script(s) is being loaded.', }, ]; return ( <DefaultLayout> <Heading title="Some of the Features of this Application." subtitle="Given below is a list of features of this application., If you have any suggestions for a feature, Just create a new issue or let me know." /> <Row type="flex" className="component--features"> { APPLICATION_FEATURES.map( (feature,index) => { return ( <Col key={index} xs={24} sm={12} md={8} className="feature-container"> <div className="feature"> <div className="title"> <div className="status">{ feature.status ? <Icon type="check-circle-o" /> : <Icon type="close-circle-o" /> }</div> { feature.title } </div> <div className="description">{ feature.description }</div> </div> </Col> ); }) } </Row> </DefaultLayout> ); } export default Features;

We have created an array of Features and we're iterating over that array., And we're also displaying a Heading for the page.

Create a new file named src/content/About.js and add this code in it

'use strict'; import React from 'react'; import DefaultLayout from 'app/layouts/Default'; import { Heading, URL } from 'app/components/UI'; import { Row, Col } from 'antd'; const About = (props) => { return ( <DefaultLayout> <Heading title="Productivity Application - Kanban Style Customizable Boards, Lists and Cards to make you more productive." subtitle="Kanban style, Trello inspired Productivity application built using the awesome React, Ant Design, Apollo Client and other fantastic libraries." /> <Col span={14} offset={5} style={{ marginTop: 40 }}> <p>For installation instructions and how to use this application, Please visit <URL to="https://github.com/dhruv-kumar-jha/productivity-frontend" /></p> </Col> </DefaultLayout> ); } export default About;

Simple page with some text and Link to the GitHub Repository.

Let's create a file named src/content/ContactUs.js and add this code in it

'use strict'; import React from 'react'; import DefaultLayout from 'app/layouts/Default'; import { Heading } from 'app/components/UI'; import { Row, Col, message } from 'antd'; const ContactUs = (props) => { const handleSubmit = (e) => { e.preventDefault(); message.info('Message sending functionality is not yet implemented.'); } return ( <DefaultLayout> <Heading title="Who doesn't love to get Feedback and Suggestions?" subtitle="We would love to hear from you., Just fill the form below and we will get in touch with you soon (if required)." /> <Col span={14} offset={5}> <div className="component__form"> <form onSubmit={ handleSubmit }> <div className="input"> <label htmlFor="name">Full Name</label> <input type="text" id="name" placeholder="John Doe" autoFocus={true} /> </div> <div className="input"> <label htmlFor="email">Email Address</label> <input type="text" id="email" placeholder="john.doe@gmail.com" /> </div> <div className="input"> <label htmlFor="message">Your Message</label> <textarea type="text" id="message" placeholder="Please enter your message here..."></textarea> </div> <button type="submit" className="button">Send Message</button> </form> </div> </Col> </DefaultLayout> ); } export default ContactUs;

We have a contact form here, But at the moment this does nothing., We wil come back to this in another Tutorial.

Let's create our final page, Create a file named src/content/PageNotFound.js and add this code in it

'use strict'; import React from 'react'; import DefaultLayout from 'app/layouts/Default'; import { Heading } from 'app/components/UI'; const PageNotFound = (props) => { return ( <DefaultLayout> <div className="component__empty"> <Heading title="Page Not Found." subtitle="The page you're looking for doesn't exist or you dont have permission to access it." /> </div> </DefaultLayout> ); } export default PageNotFound;

Now let's add the routes for all these pages we created, Open file src/routes.js and replace the existing code with this code.

'use strict'; import DynamicImport from 'app/components/DynamicImport'; const WebsiteRoutes = { childRoutes: [ { path: '/', indexRoute: { getComponent(location, cb) { DynamicImport( import(/* webpackChunkName: "home" */'app/content/Home'), cb, 'home' ); } }, }, { path: 'features', indexRoute: { getComponent(location, cb) { DynamicImport( import(/* webpackChunkName: "features" */'app/content/Features'), cb, 'features' ); } }, }, { path: 'about', indexRoute: { getComponent(location, cb) { DynamicImport( import(/* webpackChunkName: "about" */'app/content/About'), cb, 'about' ); } }, }, { path: 'contact-us', indexRoute: { getComponent(location, cb) { DynamicImport( import(/* webpackChunkName: "contact-us" */'app/content/ContactUs'), cb, 'contact-us' ); } }, }, { path: '*', getComponent(location, cb) { DynamicImport( import(/* webpackChunkName: "page-not-found" */'app/content/PageNotFound'), cb, 'page-not-found' ); } }, ], }; export default WebsiteRoutes;

Step#3: Adding Styles

Even though we have created all the pages and added all the content we need, This is just a skeleton and won't look good without any styles.

Let's rename our existing file in public/styles/style.css to public/styles/style.scss and this code in it.

/** * Our main stylesheet. */ @import url('https://fonts.googleapis.com/css?family=Shadows+Into+Light+Two'); $font_family_default: 'Segoe UI', 'Open Sans', Tahoma, Arial, sans-serif; $font_family_sil: 'Shadows Into Light Two', cursive; $font_family_lg: 'Lucida Grande', Tahoma; $header_height: 50px; $content_width: 1000px; $body_background: #F7F8FA; body { background: $body_background; font-family: $font_family_default; } .website--layout { min-height: 100vh; padding-top: $header_height; } // header styles: start header.main { height: $header_height; border-bottom: 1px solid #DDD; background: #FFF; position: fixed; left: 0; right: 0; top: 0; z-index: 10; // padding: 0 50px; border-bottom: 4px solid #F7F8FA; box-shadow: 0 0 0 1px #CCC; .container { display: flex; justify-content: space-between; } .logo { font-size: 18px; font-weight: 700; color: #000; line-height: $header_height; a { color: #000; } } nav { overflow: hidden; display: flex; a { font-weight: 700; font-size: 14px; color: #000; line-height: 24px; margin: 13px 0; &.active { color: #FF0000; } } span.sep { font-size: 18px; line-height: 24px; padding: 13px 0; color: #CCC; &:before { font-weight: 100; content: '|'; padding: 0 10px; } } } } // header styles: end .container { max-width: $content_width; padding: 0 50px; margin: 0 auto; } .page-content { padding-top: 70px; padding-bottom: 50px; overflow: auto; p { font-size: 18px; line-height: 22px; color: #000; margin-bottom: 10px; } .heading { font-family: $font_family_sil; color: #000; font-weight: 400; font-size: 30px; line-height: 34px; .subtitle { font-family: $font_family_lg; margin-top: 20px; margin-bottom: 20px; font-size: 16px; line-height: 20px; font-weight: 400; color: #999; } } } .component--slider { margin-top: 50px; .ant-carousel .slick-slide { text-align: center; overflow: hidden; } .image { max-height: 460px; img { max-width: 100%; border-radius: 4px; border: 2px solid rgb(234, 214, 33); padding: 5px; background: #FFEB3B; } } .title { font-family: $font_family_lg; color: #999; padding: 10px 20px; font-size: 16px; line-height: 16px; margin-top: 10px; margin-bottom: 20px; } .slick-dots { li { background: #000; } li.slick-active button { background: #FF0000; } } } .component--features { margin-top: 50px; .feature-container { margin-bottom: 12px; } .feature { padding: 20px; cursor: pointer; min-height: 100px; height: 100%; color: rgba(0, 0, 0, 0.7); border-radius: 3px; background: #d3f1ff; // #FEA; border: 2px solid #a2d6ef; // #FFE063; margin-right: 12px; } .title { display: flex; font-family: $font_family_lg; font-size: 18px; line-height: 100%; font-weight: 700; color: #000; letter-spacing: -1px; .status { margin-right: 10px; color: green; } } .description { font-size: 13px; line-height: 15px; margin-top: 15px; color: rgba(0, 0, 0, 0.59); } } .component__form { margin-top: 20px; .input { margin-bottom: 20px; &:last-child { margin-bottom: 0; } } label { display: block; font-size: 15px; line-height: 24px; font-weight: 700; margin-bottom: 2px; } input, textarea { width: 100%; background: #FFF; border: 1px solid #CCC; font-size: 14px; line-height: 16px; padding: 8px 14px; } textarea { resize: none; min-height: 200px; } button { padding: 8px 14px; background: #FF4848; border-radius: 2px; border: 1px solid; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); padding: 4px 10px; display: inline-block; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; color: #FFF; font-weight: 700; } } .component__empty { max-width: 600px; padding: 40px 20px; background: #ffebe4; margin: 0 auto; border-radius: 4px; border: 1px solid #ffc5b1; .subtitle { color: #000 !important; } }

Since we've used SCSS here, We will have to install another dependency sass-loader and node-sass, These modules will understand our SCSS code and parse it correctly.

Let's install these packages by running yarn add --dev sass-loader node-sass and then open webpack.config.js file and add this code in our rules object.

... { test: /.scss$/, use: ExtractTextPlugin.extract({ use: ['css-loader', 'sass-loader'] }), }, ...

Now, Open src/app.js and add this import statement

import 'public/styles/style.scss';

This will include/load our public/styles/style.scss file for webpack to process.

If you get confused, You can check the full code for webpack.config.js and src/app.js in our project repository.

Step#4: Testing Our Website

It's time to see how our website look like and if everything's working as expected.

If you already have a directory named public/scripts, delete the scripts directory.

Run the command ( in terminal ) yarn build once the build process in completed you can see the scripts directory created again with all of our Javascript files.

You can also see file named 200.html and style.css created in public directory.

Run the command yarn start this will start the server, Open the URL you see in the console and you will see something like this

Well, It wont look exactly like this because you don't have the two images we added in our Home page., But it will look similar and you can open and see all other pages as well as refresh and see the right page with right content.

Let's commit the code and push it to GitHub., In this case https://github.com/dhruv-kumar-jha/react-static-complete-website/tree/V3.0

Step#5: Publishing Our Website

Now that we have a working websitw, Let's publish it so others can access it.

For publishing we have lots of options, We can publish our website on GitHub Pages, Netlify, Amazon S3.. etc There are so many different options.

I have decided to go with Firebase as that's the hosting i am using for my own websit and it's free**.

If you don't already have a Firebase account, Create one by visiting https://firebase.google.com/ and then Log into your account.

After logging in, Goto https://console.firebase.google.com and click on Add project. Enter project name and select your Country/region. And then click on CREATE PROJECT button.

You will see the Projects Dashboard, In this case https://console.firebase.google.com/project/react-static-website/overview (This URL won't work for you.)

Now to use the Hosting provided by Firebase, We will have to install their package, Open terminal and run the command npm install -g firebase-tools This will install firebase-tools package, This will make it easier for us to deploy our website to Firebase.

After the package is installed, Open the projects directory and run the command firebase login in terminal. This will ask you to login to your Firebase acccount. Please login and you will be authenticated.

After you successfull log into Firebase., Let's run this command firebase init You will be asked few questions, Please hit Space Bar to select the option(s) and enter to continue., You will also see list of all your Firebase projects, Select the one you want to associate with this proejct.

This will create two new files in our project directory, .firebaserc and firebase.json

If you made any mistake while selecting the Firebase Project you want to associate with this project, Open .firebaserc and change your project name there.

If you open firebase.json You can see the public path to be set to public directory, This is correct and exactly what we want, However in rewrites every request is passed to index.html file. We don't have index.html file in our project (Yet).

Open firebase.json and replace its code with this code

{ "hosting": { "public": "public", "rewrites": [ { "source": "**", "destination": "/200.html" } ] } }
If you see a file named index.html in public directory, Please delete it., This was added by Firebase CLI.

After making this change, Open terminal and run the command firebase deploy. Once the deployment process is completed you can see the project URL in the terminal.

This will deploy our website live and if you open the your projects URL, you can access it., In this case its https://react-static-website.firebaseapp.com

Now open the URL, In this case https://react-static-website.firebaseapp.com and you can see the website we just created. Wonderful.

Let's commit our changes and push the code again.

You can see the updated code here https://github.com/dhruv-kumar-jha/react-static-complete-website

What's Next?

Although we have created and published the website, We've only just begun.

If you open public/scripts directory you can see all the generated Javascript files, Their size is Huge., We have to make our Javascript code production ready. We haven't even started with the SEO and we're still not pre-rendering all of our HTML pages.

These will come next.

Hope to see you in next Tutorial.

GitHub: https://github.com/dhruv-kumar-jha/react-static-complete-website

Live Website: https://react-static-website.firebaseapp.com/