Contents
Hiii, it’s me who is learning NextJs with you. We have learned about NextJs basic, now it’s time to do some real-life project with NextJs.
Let’s create a simple blog (front-end part) with NextJs. Because our aim is to convert an HTML template into NextJs app, so we just focus on template part without caring about cms/api or any thing else.
Check out these tutorials about Next.js
Next.js + Ant Design with less – Advanced Nextjs and Ant design scaffolding
Create custom self-api Next.js api server
NextJs + KoaJs Create custom NextJs server with KoaJs
[Tutorial] Create react based server side rendering app with NextJS
NextJs Data fetching – Combine Server Side and Client Side fetching
Cloudreports – Next.js tutorials
Imagine, your design/front-end team have just completed their work. You have a pure html version of your blog. Now you have to transform this html version into NextJs version… that is what we will investigate today.
To reduce time, i will use an existed template (Colorlib Fantom. Thank
Colorlib
team for sharing this beautiful template)
1. Initialize project
mkdir html2nextjs && cd html2nextjs
npm init -y
npm i -s react react-dom next
2. Create static
directory and other assets dir
NextJs has it’s built-in handler for serving static file. If a file is located at static
directory, then it can be loaded from web page without configuring custom server
or proxy
mkdir -p static/assets/template
Download Colorlib Fantom template and extract it into static/assets/template
3. Create pages and components directory
NextJs serve main pages by components inside pages
directory. This directory will become crowded when your project size grow up. Each page should only care about it’s main content; any shared partials
should be separated and storaged in other directory. We will locate them in components
. You may concern what partials
are? They will be some thing as layout
, sidebar
, header
, footer
…
mkdir pages components
4. Prepare layout file
Create file components/layout.jsx
with this content
import React from 'react'
export default class AppLayout {
render() {
}
}
every thing that belong to layout
should be returned from render
method
Open static/assets/template/fantom/index.html
in your favorite editor
In above image, the full template was collapsed in order to keep the readability of the layout. The html code inside red border rectangle is the only part that changed between pages, the other code (outside the red rectangle) is the page layout.
create file components/header.jsx
with content
import React from 'react'
export default class AppHeader extends React.Component {
render() {
return (
<header className="header_area">
<div className="logo_part">
<div className="container">
<a className="logo" href="#"><img src="/static/assets/template/fantom/img/logo.png" alt="" /></a>
</div>
</div>
<div className="main_menu">
<nav className="navbar navbar-expand-lg navbar-light">
<div className="container">
{/* <!-- Brand and toggle get grouped for better mobile display --> */}
<a className="navbar-brand logo_h" href="index.html"><img src="/static/assets/template/fantom/img/logo.png" alt="" /></a>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
{/* <!-- Collect the nav links, forms, and other content for toggling --> */}
<div className="collapse navbar-collapse offset" id="navbarSupportedContent">
<ul className="nav navbar-nav menu_nav">
<li className="nav-item active"><a className="nav-link" href="index.html">Home</a></li>
<li className="nav-item"><a className="nav-link" href="category.html">Category</a></li>
<li className="nav-item"><a className="nav-link" href="archive.html">Archive</a></li>
<li className="nav-item submenu dropdown">
<a href="#" className="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Pages</a>
<ul className="dropdown-menu">
<li className="nav-item"><a className="nav-link" href="single-blog.html">Blog Details</a></li>
<li className="nav-item"><a className="nav-link" href="elements.html">Elements</a></li>
</ul>
</li>
<li className="nav-item"><a className="nav-link" href="contact.html">Contact</a></li>
</ul>
<ul className="nav navbar-nav navbar-right ml-auto">
<li className="nav-item"><a href="#" className="search"><i className="lnr lnr-magnifier"></i></a></li>
</ul>
</div>
</div>
</nav>
</div>
</header>
)
}
}
create file components/footer.jsx
with this content
import React from 'react'
export default class AppFooter extends React.Component {
render() {
return (
<footer className="footer-area">
<div className="container">
<div className="row">
<div className="col-lg-3 col-md-6 col-sm-6">
<div className="single-footer-widget">
<h6 className="footer_title">About Us</h6>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore dolore magna aliqua.</p>
</div>
</div>
<div className="col-lg-4 col-md-6 col-sm-6">
<div className="single-footer-widget">
<h6 className="footer_title">Newsletter</h6>
<p>Stay updated with our latest trends</p>
<div id="mc_embed_signup">
<form target="_blank" action="https://spondonit.us12.list-manage.com/subscribe/post?u=1462626880ade1ac87bd9c93a&id=92a4423d01" method="get" className="subscribe_form relative">
<div className="input-group d-flex flex-row">
<input name="EMAIL" placeholder="Email Address" required="" type="email" />
<button className="btn sub-btn"><span className="lnr lnr-arrow-right"></span></button>
</div>
<div className="mt-10 info"></div>
</form>
</div>
</div>
</div>
<div className="col-lg-3 col-md-6 col-sm-6">
<div className="single-footer-widget instafeed">
<h6 className="footer_title">Instagram Feed</h6>
<ul className="list instafeed d-flex flex-wrap">
<li><img src="/static/assets/template/fantom/img/instagram/Image-01.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-02.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-03.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-04.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-05.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-06.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-07.jpg" alt="" /></li>
<li><img src="/static/assets/template/fantom/img/instagram/Image-08.jpg" alt="" /></li>
</ul>
</div>
</div>
<div className="col-lg-2 col-md-6 col-sm-6">
<div className="single-footer-widget f_social_wd">
<h6 className="footer_title">Follow Us</h6>
<p>Let us be social</p>
<div className="f_social">
<a href="#"><i className="fa fa-facebook"></i></a>
<a href="#"><i className="fa fa-twitter"></i></a>
<a href="#"><i className="fa fa-dribbble"></i></a>
<a href="#"><i className="fa fa-behance"></i></a>
</div>
</div>
</div>
</div>
<div className="row footer-bottom d-flex justify-content-between align-items-center">
<p className="col-lg-12 footer-text text-center" suppressHydrationWarning={true}>
{/* <!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. --> */}
Copyright ©<script>document.write(new Date().getFullYear());</script>
All rights reserved | This template is made with
<i className="fa fa-heart-o" aria-hidden="true"></i> by
<a href="https://colorlib.com" target="_blank">Colorlib</a>
{/* <!-- Link back to Colorlib can't be removed. Template is licensed under CC BY 3.0. --> */}
</p>
</div>
</div>
</footer>
)
}
}
update file /components/layout.jsx
to this
import React from 'react'
import Header from './header'
import Footer from './footer'
export default class AppLayout extends React.Component {
render() {
return (
<>
<Header />
{this.props.children}
<Footer />
</>
)
}
}
create file pages/_document.jsx
with this content
import Document, { Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
let props = { ...initialProps };
return props;
}
render() {
return (
<html>
<Head>
<link rel="stylesheet" href="/static/assets/template/fantom/css/bootstrap.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/vendors/linericon/style.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/css/font-awesome.min.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/vendors/owl-carousel/owl.carousel.min.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/vendors/lightbox/simpleLightbox.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/vendors/nice-select/css/nice-select.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/vendors/animate-css/animate.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/vendors/jquery-ui/jquery-ui.css" />
{/* <!-- main css --> */}
<link rel="stylesheet" href="/static/assets/template/fantom/css/style.css" />
<link rel="stylesheet" href="/static/assets/template/fantom/css/responsive.css" />
</Head>
<body>
<Main />
<NextScript />
<style dangerouslySetInnerHTML={{__html: `.owl-carousel {display: block;}.post_slider_inner.owl-carousel > .item {
display: inline-block;
width: 25%;
}`}}></style>
</body>
</html>
);
}
}
create file pages/index.jsx
that return the content part
import React from 'react'
import Layout from '../components/layout'
export default class IndexPage extends React.Component {
render() {
return (
<Layout>
{/* <!--================Post Slider Area =================--> */}
<section className="post_slider_area">
<div className="post_slider_inner owl-carousel">
<div className="item">
<div className="post_s_item">
<div className="post_img">
<img src="/static/assets/template/fantom/img/post-slider/post-s-1.jpg" alt="" />
</div>
<div className="post_text">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke + CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</div>
<div className="item">
<div className="post_s_item">
<div className="post_img">
<img src="/static/assets/template/fantom/img/post-slider/post-s-2.jpg" alt="" />
</div>
<div className="post_text">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke + CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</div>
<div className="item">
<div className="post_s_item">
<div className="post_img">
<img src="/static/assets/template/fantom/img/post-slider/post-s-3.jpg" alt="" />
</div>
<div className="post_text">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke + CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</div>
<div className="item">
<div className="post_s_item">
<div className="post_img">
<img src="/static/assets/template/fantom/img/post-slider/post-s-4.jpg" alt="" />
</div>
<div className="post_text">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke + CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</div>
</div>
</section>
{/* <!--================Blog Area =================--> */}
<section className="blog_area p_120">
<div className="container">
<div className="row">
<div className="col-lg-8">
<div className="blog_left_sidebar">
<article className="blog_style1">
<div className="blog_img">
<img className="img-fluid" src="/static/assets/template/fantom/img/home-blog/blog-1.jpg" alt="" />
</div>
<div className="blog_text">
<div className="blog_text_inner">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</article>
<article className="blog_style1">
<div className="blog_img">
<img className="img-fluid" src="/static/assets/template/fantom/img/home-blog/blog-2.jpg" alt="" />
</div>
<div className="blog_text">
<div className="blog_text_inner">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</article>
<div className="row">
<div className="col-md-6">
<article className="blog_style1 small">
<div className="blog_img">
<img className="img-fluid" src="/static/assets/template/fantom/img/home-blog/blog-small-1.jpg" alt="" />
</div>
<div className="blog_text">
<div className="blog_text_inner">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>2nd Gen Smoke Co Bomb Alarm integration</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</article>
</div>
<div className="col-md-6">
<article className="blog_style1 small">
<div className="blog_img">
<img className="img-fluid" src="/static/assets/template/fantom/img/home-blog/blog-small-2.jpg" alt="" />
</div>
<div className="blog_text">
<div className="blog_text_inner">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>2nd Gen Smoke Co Bomb Alarm integration</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</article>
</div>
</div>
<article className="blog_style1">
<div className="blog_img">
<img className="img-fluid" src="/static/assets/template/fantom/img/home-blog/blog-3.jpg" alt="" />
</div>
<div className="blog_text">
<div className="blog_text_inner">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</article>
<article className="blog_style1">
<div className="blog_img">
<img className="img-fluid" src="/static/assets/template/fantom/img/home-blog/blog-4.jpg" alt="" />
</div>
<div className="blog_text">
<div className="blog_text_inner">
<a className="cat" href="#">Gadgets</a>
<a href="single-blog.html"><h4>Nest Protect: 2nd Gen Smoke CO Alarm</h4></a>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
<div className="date">
<a href="#"><i className="fa fa-calendar" aria-hidden="true"></i> March 14, 2018</a>
<a href="#"><i className="fa fa-comments-o" aria-hidden="true"></i> 05</a>
</div>
</div>
</div>
</article>
<nav className="blog-pagination justify-content-center d-flex">
<ul className="pagination">
<li className="page-item">
<a href="#" className="page-link" aria-label="Previous">
<span aria-hidden="true">
<span className="lnr lnr-chevron-left"></span>
</span>
</a>
</li>
<li className="page-item"><a href="#" className="page-link">01</a></li>
<li className="page-item active"><a href="#" className="page-link">02</a></li>
<li className="page-item"><a href="#" className="page-link">03</a></li>
<li className="page-item"><a href="#" className="page-link">04</a></li>
<li className="page-item"><a href="#" className="page-link">09</a></li>
<li className="page-item">
<a href="#" className="page-link" aria-label="Next">
<span aria-hidden="true">
<span className="lnr lnr-chevron-right"></span>
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<div className="col-lg-4">
<div className="blog_right_sidebar">
<aside className="single_sidebar_widget search_widget">
<div className="input-group">
<input type="text" className="form-control" placeholder="Search Posts" />
<span className="input-group-btn">
<button className="btn btn-default" type="button"><i className="lnr lnr-magnifier"></i></button>
</span>
</div>
{/* <!-- /input-group --> */}
<div className="br"></div>
</aside>
<aside className="single_sidebar_widget author_widget">
<img className="author_img rounded-circle" src="/static/assets/template/fantom/img/blog/author.png" alt="" />
<h4>Charlie Barber</h4>
<p>Senior blog writer</p>
<div className="social_icon">
<a href="#"><i className="fa fa-facebook"></i></a>
<a href="#"><i className="fa fa-twitter"></i></a>
<a href="#"><i className="fa fa-github"></i></a>
<a href="#"><i className="fa fa-behance"></i></a>
</div>
<p>Boot camps have its supporters andit sdetractors. Some people do not understand why you should have to spend money on boot camp when you can get. Boot camps have itssuppor ters andits detractors.</p>
<div className="br"></div>
</aside>
<aside className="single_sidebar_widget popular_post_widget">
<h3 className="widget_title">Popular Posts</h3>
<div className="media post_item">
<img src="/static/assets/template/fantom/img/blog/popular-post/post1.jpg" alt="post" />
<div className="media-body">
<a href="blog-details.html"><h3>Space The Final Frontier</h3></a>
<p>02 Hours ago</p>
</div>
</div>
<div className="media post_item">
<img src="/static/assets/template/fantom/img/blog/popular-post/post2.jpg" alt="post" />
<div className="media-body">
<a href="blog-details.html"><h3>The Amazing Hubble</h3></a>
<p>02 Hours ago</p>
</div>
</div>
<div className="media post_item">
<img src="/static/assets/template/fantom/img/blog/popular-post/post3.jpg" alt="post" />
<div className="media-body">
<a href="blog-details.html"><h3>Astronomy Or Astrology</h3></a>
<p>03 Hours ago</p>
</div>
</div>
<div className="media post_item">
<img src="/static/assets/template/fantom/img/blog/popular-post/post4.jpg" alt="post" />
<div className="media-body">
<a href="blog-details.html"><h3>Asteroids telescope</h3></a>
<p>01 Hours ago</p>
</div>
</div>
<div className="br"></div>
</aside>
<aside className="single_sidebar_widget">
<a href="#"><img className="img-fluid" src="/static/assets/template/fantom/img/blog/add.jpg" alt="" /></a>
<div className="br"></div>
</aside>
<aside className="single_sidebar_widget post_category_widget">
<h4 className="widget_title">Post Catgories</h4>
<ul className="list cat-list">
<li>
<a href="#" className="d-flex justify-content-between">
<p>Technology</p>
<p>37</p>
</a>
</li>
<li>
<a href="#" className="d-flex justify-content-between">
<p>Lifestyle</p>
<p>24</p>
</a>
</li>
<li>
<a href="#" className="d-flex justify-content-between">
<p>Fashion</p>
<p>59</p>
</a>
</li>
<li>
<a href="#" className="d-flex justify-content-between">
<p>Art</p>
<p>29</p>
</a>
</li>
<li>
<a href="#" className="d-flex justify-content-between">
<p>Food</p>
<p>15</p>
</a>
</li>
<li>
<a href="#" className="d-flex justify-content-between">
<p>Architecture</p>
<p>09</p>
</a>
</li>
<li>
<a href="#" className="d-flex justify-content-between">
<p>Adventure</p>
<p>44</p>
</a>
</li>
</ul>
<div className="br"></div>
</aside>
<aside className="single-sidebar-widget tag_cloud_widget">
<h4 className="widget_title">Tag Clouds</h4>
<ul className="list">
<li><a href="#">Technology</a></li>
<li><a href="#">Fashion</a></li>
<li><a href="#">Architecture</a></li>
<li><a href="#">Fashion</a></li>
<li><a href="#">Food</a></li>
<li><a href="#">Technology</a></li>
<li><a href="#">Lifestyle</a></li>
<li><a href="#">Art</a></li>
<li><a href="#">Adventure</a></li>
<li><a href="#">Food</a></li>
<li><a href="#">Lifestyle</a></li>
<li><a href="#">Adventure</a></li>
</ul>
</aside>
</div>
</div>
</div>
</div>
</section>
</Layout>
)
}
}
Some important notes:
- You must replace all
href
andsrc
value to the right assets path. For example, an image element<img src="img/post-slider/post-s-1.jpg" alt="" />
should be changed to<img src="/static/assets/template/fantom/img/post-slider/post-s-1.jpg" alt="" />
- Replace all
class
attribute toclassName
soReact
can render correctly - In
Footer.jsx
, i addedsuppressHydrationWarning={true}
toCopy right tag
. Inside this tag, we havedocument.write(new Date().getFullYear());
, when server rendering, it will response as an empty text. But at client side, it print the current year. This difference is normal but the goal of React SSR is to bring SSR and CSR more closer, so if these values is difference, React will show the warning - Look at
_document.jsx
, this file is used for overriding defaultNextJs
document markup. In this file, i kept all css file but remove all js files. Css is not effect to the dom but JS does. If we keep js files here, they may cause our dom to render incorrectly. While working in real-life projects, you should consider to replace the use of these js files by using React component. In this example, to keep thing simply, i choose to remove all js files; it cause theOwl slider
stop working, so i putted some css code to make the slide appear
After completed all above steps, we have a home page look like this. Almost the same as html version
Now we can continue on converting other pages. This is a simple blog, so it just contain these type of page:
- Home page (index.html)
- Archive (archive.html)
- Contact (contact.html)
- Elements show case (elements.html)
- Blog detail (single-blog.html)
Continue converting these pages as above steps…
Git repository for this tutorial:
Thank you!
Discussion about this post