This yr I made a decision emigrate my weblog from Gatsby to NextJS. To have the ability to add options extra simply.
I exploit ChakraUI as a design system, Google Analytics for metrics, Netlify to host it and Typescript as language.
Create the challenge
Within the first model of the weblog, I wrote posts in markdown. I have to hold it. As a developer, it’s simpler than sustaining a WordPress (or different) back-end.
NextJS
Making a NextJS challenge, it’s simple, one command is sufficient. You add your challenge title, go to the challenge folder and begin the dev atmosphere.
npx create-next-app@newest --typescript
The challenge comprises basic information of a typescript challenge. And a few necessary information for NextJS too.
-
subsequent.config.js
: the file used for the configuration - the
pages
folder: comprises an index file for the foundation web page - the
_app.tsx
file: contained in thepages
folder, it comprises the part used to initialize pages. - the
public
folder: used for all static information, like favicon and pictures.
In NextJS, every folder or file contained in the pages folder is a route. You possibly can observe all of the routes by unfolding the tree.
Helpful packages
Once I code, I like to make use of helpful packages.
- Prettier: to format my code. In Visible Studio Code, I exploit it with autosave.
- Husky: Prettier codecs all supported staged information earlier than the commit.
- Eslint: it offers a bunch of guidelines for linting your Typescript and Javascript information.
- Stylelint: identical as Eslint, however for CSS and SCSS information.
- Markdownlint: one other linter, for the markdown information.
- Sass: to assist SCSS information.
Chances are you’ll assume that’s an excessive amount of. Utilizing linters and code formatting instruments are time savers.
When your challenge grows in measurement, you’ll encounter bugs. Consuming psychological load to test semicolons and code indentation is a waste of time. Ahead these items to instruments and hold your time to write down new options and repair bugs.
All these packages want a configuration file.
These configurations come from my expertise on totally different tasks. They are often outdated for those who learn this publish lengthy after its launch.
Construction of my app
For my React tasks, I exploit a construction contained in the src
folder. It’s to dispatch information in keeping with their function. Naming can change relying on the challenge.
- a
generic
folder: for every part you need to use in every challenge with out adjustments. - a
enterprise
folder: for all of the options of the challenge. Every folder inside it’s a function. - a
core
folder: for the remainder of it. Configuration information, particular or shared providers.
That is my first NextJS challenge and never having an src
folder is disorienting. However adapting is a part of the developer’s ability set. I outline a brand new construction. As every first time, it may well’t be the very best one, however I’ll enhance it later.
- the
pages
and thepublic
folder: it’s utilized by the framework, so I have to hold them. - the
elements
folder: every folder inside it comprises a part and all I want for it. - the
hooks
folder: comprises all of the customized hooks utilized by the elements. - the
core
folder: all I want for the app, like API, utilities, the customized theme for Chakra, and so forth. - the
posts
folder: comprises all of the markdown information
If in case you have a number of elements, like me in the beginning of the challenge, don’t over-engineer your challenge. You don’t want loads of empty folders and information.
Implement ChakraUI
So as to add ChakraUI to the challenge, I exploit this command, because it’s defined within the documentation.
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
To make use of ChakraUI, you will need to add a part referred to as ChakraProvider
. This part has a theme
property, to override the ChakraUI default theme. You will need to add it contained in the pages folder, within the _app.tsx
file.
I create a customized theme file to increase colors and add fonts.
import { extendTheme } from "@chakra-ui/react";
const mainTheme = extendTheme({
colours: {
model: {
darkBlue: "#1f4f6f",
blue: "#22577a",
greenBlue: "#38a3a5",
greener: "#57cc99",
inexperienced: "#80ED99",
lightGreen: "#c7f9cc",
},
},
fonts: {
heading: `'Raleway', sans-serif`,
physique: `'Libre Baskerville', sans-serif`,
},
});
export default mainTheme;
It is my first bug. I exploit SCSS import
to fetch the google fonts. NextJS V12 use SWC as a minifier software. There’s a bug with this model and import
does not work in manufacturing.
To resolve this, I do a Google search and discover the answer right here.
I exploit the particular file _document.tsx
. The construct updates the render of the html
and physique
tags on this file.
I add right here all of the scripts really helpful by the Google fonts web site. And it really works.
import Doc, { Html, Head, Major, NextScript } from "subsequent/doc";
export default class MyDocument extends Doc {
render(): JSX.Ingredient {
return (
<Html>
<Head>
{/* Google fonts */}
<hyperlink rel="preconnect" href="https://fonts.googleapis.com" />
<hyperlink rel="preconnect" href="https://fonts.gstatic.com" />
<hyperlink
href="https://fonts.googleapis.com/css2?household=Libre+Baskerville&household=Raleway:wght@800&show=swap"
rel="stylesheet"
/>
</Head>
<physique>
<Major />
<NextScript />
</physique>
</Html>
);
}
}
CSS in JS
ChakraUI makes use of CSS properties as props for elements. At first, I exploit SCSS first and props after.
It’s a mistake. I’ve chosen a design system to achieve time. Regardless that I misplaced time throughout the studying curve I’ll retrieve it afterwards. So I have to exploit it at 100%. I’ll delete most styling information and attempt to hold solely the SCSS file for the posts.
CSS in JS is straightforward to make use of. As a substitute of utilizing CSS lessons, you add the property to the part. In CSS, I write properties in kebab-case (lowercase and separated with hyphens). A React property can’t include a hyphen. As a substitute, they’re in camel-case. Chakra offers us some shortcuts too, like justify
for justifyContent
.
Generally we want media queries. It’s for responsive design just like the flexbox path property above. Chakra offers us two options: an array syntax and an object syntax. Utilizing the article syntax is clearer. Keys are the totally different breakpoints.
<Flex
width="100%"
padding={{ base: "0.5em", lg: "6em" }}
align="heart"
justify={"space-evenly"}
backgroundColor="model.darkBlue"
minHeight="80vh"
path={{ base: "column-reverse", lg: "row" }}
>
From markdown to the publish
The primary model of the weblog used Gatsby and a starter (like a template). At construct, the engine makes use of GraphQL requests for posts. I haven’t had the time to know how Gatsby makes use of GraphQL. So I left it out.
NextJS makes use of capabilities to learn the posts and render them as props. To assist, NextJS offers us a starter. I exploit it to know learn how to proceed with markdown information.
First, we should execute this command to put in the packages we want.
yarn add prismjs comment remark-html remark-prism gray-matter @varieties/remark-prism
It’s time to speak about getStaticProps
and getStaticPath
. It’s 2 particular capabilities of NextJS.
getStaticProps
It’s used for static web site technology. This perform run throughout the construct, to generate static pages. The distinctive parameter is context
.
I exploit it to get the final a part of the URL named slug
. I give it to capabilities which return the present publish and a few knowledge concerning the subsequent and the earlier publish.
getStaticProps
return an object utilized by the part to render the posts web page.
export async perform getStaticProps({ params }: Params) {
const publish = getPostBySlug(params.slug, [
"title",
"date",
"slug",
"author",
"content",
"coverImage",
"ogImage",
"tags",
]);
const content material = await markdownToHtml(publish.content material || "");
const earlier = getPreviousPost(params.slug);
const subsequent = getNextPost(params.slug);
return {
props: {
publish: {
...publish,
content material,
},
earlier,
subsequent,
},
};
}
getStaticPaths
It’s used for static web site technology while you use dynamic routes. It builds all of the static paths obtainable throughout the construct.
I exploit it to retrieve all of the posts slug
and get all of the paths of my posts throughout the construct.
export async perform getStaticPaths() {
const posts = getAllPostsByDate(["slug"]);
return {
paths: posts.map((publish) => {
return {
params: {
slug: publish.slug,
},
};
}),
fallback: false,
};
}
The posts API
That is the principle a part of the method. How can we get a JSON object with the publish’s knowledge from a markdown file?
The getPostBySlug
perform will take as a parameter a string and an array. It represents the title of the file with out extension and an inventory of fields. It comes from the final a part of the URL. The perform reads the file from the posts folder. The gray-matter
library divides the info between the content material and the metadata. The perform returns an object to getStaticProps
. Fields and content material are the keys of this object.
export perform getPostBySlug(slug: string, fields: string[] = []) {
const realSlug = slug.substitute(/.md$/, "");
const fullPath = be a part of(postsDirectory, `${realSlug}.md`);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { knowledge, content material } = matter(fileContents);
sort Objects = {
[key: string]: string;
};
const gadgets: Objects = {};
fields.forEach((subject) => {
if (subject === "slug") {
gadgets[field] = realSlug;
}
if (subject === "content material") {
gadgets[field] = content material;
}
if (typeof knowledge[field] !== "undefined") {
gadgets[field] = knowledge[field];
}
});
return gadgets;
}
The markdownToHtml
perform makes use of the comment
library. It transforms the worth of the content material subject into textual content with HTML tags.
The final library I exploit there’s PrismJS. This library spotlight code elements of the publish for higher understanding.
export default async perform markdownToHtml(markdown: string) {
const end result = await comment()
.use(html, { sanitize: false })
.use(prism)
.course of(markdown);
return end result.toString();
}
Create an inventory of posts from tags
Every of my posts comprises tags. You click on on a tag and also you’re redirected to a devoted web page with filtered posts.
It is a 2 steps function. Keep in mind the getStaticPaths
, it is going to create all of the paths from the record of tags. We use it to get the record of all tags and create all of the wanted paths throughout the construct. All is on this perform.
export perform getAllTags(): Array<string> {
const allPosts = getAllPosts(["slug", "tags"]);
const flattenTags = allPosts.map((publish) => publish?.tags).flat();
const allTags = flattenTags.filter(
(merchandise, pos) => flattenTags.indexOf(merchandise) == pos
);
return allTags;
}
Let me clarify. Every publish comprises an array of tags. I exploit a map perform to retrieve them. The flat perform concatenate my array of arrays. So in flattenTags
, I’ve an array of tags with duplicates. Within the subsequent perform, I exploit a filter to take away all duplicates.
export perform getPostsByTag(tag: string, fields: string[] = []) {
return getAllPostsByDate(fields).filter((publish) => publish.tags.contains(tag));
}
The second half retrieves the record of posts sorted by date. It retains the posts if the array of tags contains the requested tag.
This API wants some enhancements, like lowercase tags.
Add Metrics
I wish to have metrics on websites. I can know which content material is hottest, the very best measurement of the publish, and so forth.
I exploit Google Analytics on the primary model of the location with a Gatsby plugin. With NextJS, I’m unable to maintain the identical software. After some Google analysis, I discover what I’m in search of.
{
isProduction && (
<>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
perform gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
</>
);
}
This snippet of code contained in the _document.tsx
file hundreds the script with my monitoring ID. The isProduction
boolean allow it if NODE_ENV
variable is about to manufacturing.
Add Sitemaps
Every web site should have sitemaps and robots.txt information.
It helps serps to index your web site. I add the next-sitemap bundle to carry out this. It runs after the construct to gather all routes and create devoted information. I add the bundle command the postbuild
command within the bundle.json
file and a configuration file.
After the deployment, sitemaps and robots.txt information will likely be obtainable.
/** @sort {import('next-sitemap').IConfig} */
module.exports = ;
Deployment
The earlier model is already in manufacturing. I wish to deploy the brand new model with out creating duplicate content material.
It’s simple with Netlify. I create a brand new app within the dashboard and hyperlink it to my repository in 2 clicks. Netlify detects my app as a NextJS challenge and set every part for me I take away the area within the first model. I add it to my new web site in area settings.
I deploy the brand new model in lower than 10 minutes.
Conclusion
I study a whole lot of issues by doing this rewriting of my web site. I did some issues improper with my lack of expertise with NextJS. However working as a developer is so fascinating. You possibly can study by apply and progress.
Including new options will assist me to search out bugs and enhancements. I resolve to open supply the repository. It’s obtainable on Gitlab and Github.
You possibly can observe the account of the location and mine on Twitter. I’ll publish superb information about subsequent options.
See you quickly!