You can checkout Part #1 of this tutorial here Building a Static Website Using React JS: Part #1 Project Setup and Website UI
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.
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.
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,
};
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;
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.
webpack.config.js
and src/app.js
in our project repository.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
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"
}
]
}
}
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
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/