When building a fullstack app using JavaScript, generally React is used on the frontend with something like ExpressJS on the backend. This involves a lot of “reinventing the wheel” configuration. Next abstracts all this away from you and allows you to focus on writing your business logic. Although at the surface it might look very similar to something like Gatsby or Create React App, there’s a lot of functionality and power that Next has.
This post is a collection of my notes on NextJS and will keep updating.
NextJS vs React
- React is more of a “view library”. You cant quickly build a complete modern app with just React, you need routing, a build system, a way to style things, performance, etc.
- You have to make a lot of decisions.
- NextJS is a full stack framework that uses React as it’s view library. It has a lot of opinionated conventions baked in that make decisions for you.
- Since Next is a full stack framework, it needs to be hosted on a platform that supports Node.
What you get with NextJS
- Dev build system (CRA also gives you this by the way)
- Production build system
- Prerendering
- Code Splitting
- Routing
- API Routes
What about CRA?
- CRA also has opinionated conventions baked in (mostly the build system)
- But CRA is more of a boilerplate for React. It doesn’t add any new functionality like routing, or SSR.
What about Gatsby?
- Gatsby is similar to NextJS.
- But, Gatsby is not a full stack framework, its just a static site generator.
- It doesn’t support SSR. Instead, gatsby is SSG.
- Gatsby also has extra features baked in like GraphQL support which Next does not have.
Routing
- Next.js routing is file system based, meaning that all you have to do to create new routes is create a new file.
- This is good, because it takes out the burden on you to decide what kind of Routing solution you want to use, how you want to set it up, etc. If you are working in a team, then you have to make sure everyone is on the same page with the convention decisions you make. This way, Next makes the decision for you.
- All you need to do to create new routes is to add new files to the
pages
folder. - For nested routes, i.e. for something like
/user/abc
, you can create a folder user
inside pages
and add abc.js
in that. - For dynamic routes, i.e. when something is a parameter in the URL, for example
/user/1
, where 1 is a parameter, Next allows a /user/:id
route. Then, you just create a [id].js
file inside user
directory for this. - For catch-all routes, i.e. when you want to “catch” all routes that come to
/user/**
, you can make a file [...param].js
inside user
folder. Note that this will not catch /user
path. For that, you’ll have to create /user/index.js
. - For optional catch-all routes, create a file
user/[[...param]].js
this will catch all routes including /user
. - The entry point to Next.js is
pages/_app.js
. This is already created for you, but you can create it manually to do something like, say import global styles.
import React from 'react';
export default function App({Component, pageProps}) {
return <Component {...pageProps} />
}
Navigation
Client Side Routing
- Next provides
Link
component for client side routing. This is similar to those in react-router or in Gatsby - The Link component takes
href
prop which is name of the page we want to navigate to - For navigating to dynamic route →
<Link href="/notes/[id]" as="/notes/1">Note 1</Link>
- For routing somewhere programatically,you can use
useRouter
const router = useRouter()
<button onClick={() => router.push('/')}>
Go home
</button>
<button onClick={() => router.push('/notes/[id]', '/notes/1')}>
Note 1
</button>
Styling
- Next comes with some style conventions baked in. And since Next uses React, you can also use any other mechanism that works with React to style your apps
- Next supports CSS Modules, which allows you to write component level stylings.
- All you have to do is create css files as
xyz.module.css
and then import this file in any component you want. Once you do that, the file will be scoped to that particular component.- Note that css modules doesn’t allow you to use pure css selectors like
body { ... }
Customizing Next.js
- You can customize the build system, extend next’s features, or add env vars using the
next-config.js
file. - Either export an object or a function
module.exports = {
webpack : {
},
env : {
MY_ENV_VAR: "var value here"
}
}
- You can also include
phase
and { defaultConfig }
as arguments
const { PHASE_PRODUCTION_BUILD, PHASE_DEVELOPMENT_SERVER } = require('next/constants')
module.exports = (phase, { defaultConfig }) => {
if (phase === PHASE_PRODUCTION_BUILD ) {
} else if (phase === PHASE_DEVELOPMENT_SERVER) {
console.log("I'm in dev!")
}
return defaultConfig;
}
Plugins
- Plugins are just a way in whichyou can configure the next config file
- Most next plugins follow the
withPluginName
format - You can compose several plugins and also your custom config object
API Routes
- Next.js is a full stack framework, you can also create endpoints
- For creating endpoints, create a folder
pages/api
. Whatever JS files you put in there, it will be treated as an endpoint - Those files are server side only folders and won’t be part of client bundle size
API Handler to split logic based on HTTP method
- You can use
next-connect
middleware to do different things based on what HTTP method is being used
import nc from 'next-connect'
import cors from 'cors'
const handler = nc()
.use(cors())
.get((req, res) => {res.send("get req"})
.post((req, res) => {res.send("post req"})
- you will get url params in
req.query.paramName
Fetching data in Next
- When do you need the data
- You can fetch data client side (at runtime) in a normal way that you do in React, i.e. used
fetch
- For fetching data ahead of time, i.e. while prerendering your pages, you have three options -
getStaticProps
, getStaticPaths
, getServerSideProps
- Note that these methods are to be used only in prerendering pages, you cant use them in components or client side data fetching
getStaticProps
- By having your page export a
getStaticProps
function, Next will run this function at build time.- Whatever you do in the function will only run on the server. The code won’t even be bundled with the client’s code.
- That means you can do things like connecting to DB, fetching something from a file system, or even crawling a website within the function
- Whatever you return from this function will be passed as props into the page component.
- The way this works internally is the results of this function are saved into a JSON file and passed as props to the page component at runtime
const IndexPage = () => { }
export default IndexPage
export async function getStaticProps(context){
return {
props: {}
}
}
getStaticPaths
- Let’s say you have a dynamic URL page, i.e. something like
/users/[id]
. And say you need the value of the param in order to fetch the content of our page inside of getStaticProps, then that’s where getStaticPaths comes in. - getStaticPaths is very much like getStaticProps, but its responsibility is to fetch all the generated paths for your URLs.
const IndexPage = () => { }
export default IndexPage
export async function getStaticPaths() {
const results = await fetch('/api/posts')
const posts = await results.json()
const paths = posts.map(post => ({params: {slug:
post.slug}}))
return {paths}
}
export async function getStaticProps({ params }) {
const res = await fetch(`/api/post/${params.slug}`)
const post = await res.json()
return {
props: {post}
}
}
- If the page isn’t dynamic, you can directly use getStaticProps. But if the page is dynamic, you’ll need to use getStaticPaths and then getStaticProps
getServerSideProps
- This function gets called at runtime during every request. This will run on the server (Node) .
- Unlike
getStaticProps
, you will have the runtime data like query params, HTTP headers, and the req and res objects from API handlers, so it is similar to an api endpoint function. - Note that
getServerSideProps
is blocking, which means that your page won’t get loaded until getServerSideProps function is completed.
const IndexPage = () => { }
export default IndexPage
export async function getServerSideProps() {
const response = await fetch(`https://somedata.com`)
const data = await response.json()
return { props: { data } }
}
- It’s recommended to use
getServerSideProps
only when you absolutely need it.- As its going to run every time there’s a request, it can make your webapp slow.
- A usecase when you would have to use it is, say, you have dynamic data that is always changing (so its not available at build time) but at the same time you also want it to be indexed by search engines, then SSR is the way to go
When to use which data fetching option
- Do you need data at runtime, but don’t need SSR?
- Use client side data fetching (using something like Fetch API or axios)
- Do you need data at runtime but do need SSR?
- Do you have pages that rely on data that is cachable and accessible at build time, for ex from a CMS?
- Do you have the same as above (accessible at build time) but the pages have dynamic URL params?
- Use
getStaticProps
and getStaticPaths
at the same time
Rendering Modes
- Next will look at the data fetching in your page components to determine how and when to prerender your page.
Static Generation
- Pages built at build time into HTML.
- CDN Cacheable. Doesn’t need a server.
- Good for things like blogs, info sites, etc. Basically any time you have sites where the data is not being changed by the user, you should statically generate it.
- Any non user generated and static content should be statically generated.
Server-side Rendering
- Pages built at the runtime into HTML.
- Cached after the initial hit.
- This has to be computed at runtime on server.
- Sometimes, you need to skip rendering some component on the server because it depends on the DOM API, or it depends on client-side data
- Next.js supports dynamic imports that, when used with components, will opt out of SSR
Client-side Rendering
- Single Page App (SPA)
- You just send JS script to the browser, where it will bootstrap the whole app
- Bad for SEO, since the content isn’t ready and JS has to execute before the content is ready.
Deciding rendering mode
- By default, all pages will be prerendered, even if they don’t export a data fetching method.
- You choose the prerendering method (static or SSR) by what data function you export in your page component.
- if you export
getStaticProps
it’s gonna do static generation at build time- also, if there is absence of
getServerSideProps
or getInitialProps
in a page, then next will automatically determine that it can prerender this page- Note that if you have a custom
App
with getInitialProps
then this automatic optimization will be turned off in pages without static generation
- if you export
getServerSideProps
it’s gonna do server side rendering- If getServerSideProps or getInitialProps is present in a page, Next.js will switch to render the page on-demand, per-request (meaning Server-Side Rendering).
- For client side rendering, fetch your data inside your components. That’s react, Next doesn’t touch React.
- You can mix and match these rendering modes to have a genuinely hybrid apps.
Custom pages
_app.js
- Wrapper for whole application
- Can be used for things like
- Persisting layout between page changes
- Keeping state when navigating pages
- Custom error handling using
componentDidCatch
- Injecting additional data into pages
- Adding global CSS
- Props for this →
{ Component, pageProps }
_document.js
- For custom Document
- Used to augment our app’s
<html>
and <body>
tags - You need to export a class that extends
Document
class from next/document
- Default
Document
which next gives us:
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
- Note that this
Document
is only rendered on the server, so event handlers like onClick
will not work here - React components outside of will not be initialized by the browser
- Document’s getInitialProps function is not called during client-side transitions, nor when a page is statically optimized.
- Document currently does not support Next.js data fetching methods (like getStaticProps, getServerSideProps, etc)
- The
<Head />
component used here is not the same one from next/head. - The
<Head />
component used here should only be used for any <head>
code that is common for all pages. - For all other cases, such as
<title>
tags, we recommend using next/head in your pages or components.
That’s it for this post. I’ll keep updating it as I discover more about the Next ecosystem. Thanks for reading and see you next time!