Introduction
As static site generation becomes more and more popular so does the need for the right tools and frameworks.
More often than not there are times when we need lightweight tools such as React to consume a decoupled service and serve static pages. There are a lot of benefits of having a tool to generate static pages for various different reasons such as speed, caching using CDN, SEO etc.
The purpose of this blog post though is to explain Next.js fundamentals - this great React framework that is growing in popularity. We will pick some of its features and will explain with examples and by doing so hopefully it will give readers a good grasp of the framework.
Assumption
As we will explain some of the Next.js features starting from beginning, the assumption is that you are familiar with React or JavaScript.
Prerequisites
Next.js requires Node.js to be installed. If you have already installed Node.js to check the version you have run node -v
on your terminal and compare with the latest version Node.js - the minimum supported version is Node.js 12.22.0.
What is Next.js
Next.js is a React based framework built on top of Node.js, with Next.js you can do a wide range of things from creating APIs to consuming external/internal APIs to server side rendering, static generation and a lot more.
As we know React is a hugely popular library, see some statistics here, but it is only the view in the MVC (Model View Controller). Next.js on the other hand is framework that is built on top of React and Node.js. The React documentation makes mention of Next.js as one of recommended toolchain.
Basic Features
There are lots of features that Next.js supports out the box but here I will only focus on some of them such as:
- TypeScript
- Code Splitting
- Routing
- Static Generation
- Data fetching
Without further ado let’s get started on creating the application.
Creating a Next.js Application
Next.js supports TypeScript out of the box, which means you don’t need to do any additional configuration.
To create we can use the following command:
npx create-next-app
# or
yarn create next-app
For those of you not familiar, npx
is a package runner and CLI tool which makes it easy to
install and mange dependencies hosted in npm registry. yarn
is a package manager.
TypeScript
As we mentioned above TypeScript is fully supported in Next.js and to create an application that uses TypeScript you would type the following command:
npx create-next-app --ts
#or
yarn create next-app --typescript
As you can see we use --ts
and --typescript
flags to tell the CLI tool create-next-app
to create the
application using TypeScript.
Now let’s create our application by navigating to your preferred directory and type this command on your terminal:
npx create-next-app nextjs-with-typescript --ts
nextjs-with-typescript
is the name of our application.
The above script will install all the necessary dependencies and when it finishes will print in the screen some commands that you can use to run the application. That’s all it is to that, you will now be able to run the application without extra configuration that you would normally need in order to compile TypeScript.
Code Splitting
Code splitting is an optimisation technique that splits the code in chunks or small bundles which then can be loaded on demand or in parallel, this way it enables the application to load a lot faster.
The importance of code splitting can be best seen on growing application, as the application grows so does the size of the JavaScript file.
Next.js Code Splitting
Next.js has built-in support for code splitting, that means you don’t have to use any external plugins such as babel.
When loading the page Next.js only loads the JavaScript necessary for that particular page.
Next.js does this by analysing the resources that is importing. If for example one of your pages
makes use of axios
library, then that specific page will include axios
in its bundle. In this way
we make sure that we only send the JavaScript needed to the client.
Next.js also supports dynamic import(), this feature makes it possible to import
JavaScript modules dynamically and load each import as a separate chunks. To get an
understanding how that is done you can have a look at your application’s built directory
which is .next
.
As we mentioned earlier, when you create the application it will generate a README file which has some basic information such as how to run and build the application, for convenience, I’ll list some commands here:
# to run the application
npm dev
# or
yarn dev
# to build the application
npm build
# or
yarn build
In order to see the built folder you would need to build your application.
Once that done you can then navigate to .next
directory, there you will see something
like the following:
|- chunks
|- {someNumber}.{hash}.js
|- commons.{hash}.js
|- runtime
|- main-{hash}.js
|- webpack-{hash}.js
|- {hash}/pages
|- _app.txs
|- _error.txs
|- index.txs
As we can see from the above, the code splitting is done by chunks, runtime and by page.
Routing
Routing is another feature that Next.js supports out of the box. Next.js uses the file system
to enable routing, every file that you put under the pages
directory with the extension
.js
, .jsx
, ts
or tsx
automatically becomes a route.
Pages
A Next.js page is a React Component, in
our application that we created earlier on we are going to go ahead and create a
page under pages
directory called about.tsx
as follows:
// file pages/about.tsx
function About(){
return <h1>About</h1>
}
export default About
The above is a React Component that simply returns a h1
heading. Now If we run the application
and go to /about
route, you will see the about page that has About
tag that we just created.
That’s how easy it is to create a route.
Index Routes
In Next.js a file named index.ts
or index.js
in the root directory of any directory under
the pages
directory, will automatically be a route.
Here are some examples:
- Creating an
index.ts
page atpages/index.ts
will create a route/
. - Creating a page at
pages/blog/index.ts
will create a route at/blog
Nested Routes
If we need to create a nested structure then under pages
directory we would create directories and
files which would then map to the routes. Here are some examples:
pages/articles/my-first-article.tsx
will create a route/articles/my-first-article
.pages/admin/settings/user.tsx
will create a route at/admin/settings/user
To demonstrate this we will create the following two pages in our app.
// file pages/articles/my-first-article.tsx
function MyFirstArticle(){
return <h1>My First Article</h1>
}
export default MyFirstArticle
// file pages/admin/settings/user.tsx
function User(){
return <h1>User</h1>
}
export default User
Now if you run the application and navigate to /articles/my-first-article
or /admin/settings/user
you will see the above components being served respectively.
Dynamic Routes
As explained above routes are defined based on the file and folders that we create
under pages
i.e. every file in there maps to a route.
Having said that, there are lots of cases in more complex applications that predefined routes are not enough and hence where the dynamic routes come in.
To create dynamic routes you can use square brackets in the name of the file
like so [param]
.
In the following we are going to create a dynamic route so when people go to articles/<id>
,
the article id will be sent as a query parameter to the page, in turn we can get this article id
using router query object and do further processing.
Let’s start by creating [aid].tsx
file under pages/articles/
like the following:
// file pages/articles/[aid].tsx
import {useRouter} from 'next/router'
const Article = () => {
const router = useRouter()
const {aid} = router.query
return <p>Article id: {aid}</p>;
}
export default Article;
The route articles/3
will be matched by [aid].tsx
so now If you go to
articles/3
it will display Article id: 3. The route articles/3
will
have this query object {aid:'3'}
. The id
can be anything that serves best your
needs that is to say it can be a string a number etc.
If the route has articles/3?foo=bar
then router query object will have {foo:'bar', aid:'3'}
, i.e. if
you do console.log(router.query)
you will then see the above values.
Nested Multiple Dynamic Routes
In cases where you need two levels of the route to be dynamic such as articles/3/a-comment
then you would create a folder and another file under that folder. Say for example
you want to capture the article id and its comment something like this
http://localhost:3000/<id>/<comment>
then in this case would create a directory [aid]
under the
pages
and a TypeScript file under [aid]
so then you would have this structure
pages/articles/[aid]/[comment].tsx
.
Then in the [comment].tsx
file put the following:
// file pages/articles/[aid]/[comment].tsx
import {useRouter} from 'next/router'
const Comment = () => {
const router = useRouter()
const {comment} = router.query
return <p>Comment: {comment}</p>;
}
export default Comment;
If you go to this route now articles/3/a-comment
the query will have
{comment:'a-comment', aid:'3'}
As inside directory [aid]
we are mapping several routes then one way to tackle this
to create an index file inside [aid]
directory which would match /article/<id>
and another
one in our case [comment].tsx
to map articles/<id>/<comment>
. The folder structure would then
be like the below:
.
├── [aid]
│ ├── [comment].tsx
│ └── index.tsx
└── my-first-article.tsx
Catching all Routes
In cases when you want to catch all routes, then first you would create a file under
the preferred directory in our case pages/[...slug]
like so:
// file pages/[...slug].tsx
import {useRouter} from 'next/router'
const CatchAll = () => {
const router = useRouter()
return <p>This page catches all routes</p>
}
export default CatchAll;
If you now run the application and navigate to http://localhost:3000/a
it will display
the page that we created above.
If you navigate to http://localhost:3000/a/b
and observer the query like so console.log(router.query)
then
you will notice that slug now has the following:
{
"slug": [
"a",
"b"
]
}
Static Generation
One of the great features that Next.js has is static site generation. There are a lot of frameworks can generate static sites, but what makes Next.js different to other SSG frameworks is the fact that Next.js is hybrid tool which can generate HTML/CSS/JavaScript at run time as well as at build time, this and lots of other features makes Next.js a truly great React SSG framework.
Next.js has two form of pre-rendering, Static Generation and Server-side Rendering. The difference between the two is when static assets, such as HTML, JavaScript, CSS etc., are generated.
With the Static Generation HTML pages are generated at build time and after that they are reused at each request, whereas with Server-side Rendering the pages are generated at each request.
If Static Generation is used pages will be generated when you run next build
and from there
you can use a CDN if you want to cache the assets.
Static Generation without Data Fetching
This is a simple rendering of static pages such at the page that we have created at the
beginning my-first-article.tsx
function MyFirstArticle(){
return <h1>My First Article</h1>
}
export default MyFirstArticle
Once we created this page and build the application this page will be available as am HTML page.
Data Fetching and Static Generation
In cases where you have to fetch the data from an API or similar then you would
use built-in function called getStaticProps
to fetch the data at build time and
then serve static pages.
If you are also catching paths then you would need to use getStaticPaths
built-in function.
This function can be used in addition to the getStaticProps
.
Data Fetching and Static Generation Working Example
In the following will got through an example to illustrate this. Let’s say we are
capturing article data from an API the way we would implement that is to first fetch
the data using getStaticProps
then we would have another function to consume those data.
In the following example we will use an example endpoint https://jsonplaceholder.typicode.com/posts
that has random posts.
function Article({props}) {
// Here you can further work with props and manipulate data as required
}
export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const articles = await res.json()
return {
props: {
articles,
}
}
}
export default Article;
In the above functions we are catching all the data from the endpoint and returning
them as props
, then in turn we are passing the props
to the above function as a parameter where
we can do further processing.
Conclusion
If you are familiar with JavaScript, Next.js is very easy to use and learn. Out of the box support for TypeScript, server-side rendering, static page generation and lots more, makes Next.js a very strong contender to use for your next project, be it a blog or a complex application that consumes an internal/external API.