Next.js: the new normal
Shihabudheen US
Posted on August 31, 2020
Next.js is a full-stack framework based on React.js.
What it offers:
- Prerendering: the entire HTML is created in the server and send to the client. So the client receives the HTML rather than the JS file. Once the HTML(string) is available it gets rehydrated at the client side. One can think of rehydration as adding event listeners and making it interactive. All the routes are pre-rendered by default.
Scripts
The common commands used to run and build a next project are the following.
"scripts":{
"dev": "next", // start in dev mode
"build": "next build", // start production build
"start": "next start" // run production build
}
Routing
using
reach-router
under the hoodfile-system-based routing
for that, we create a special folder called
pages
all the folder/file names become the routes for those files
we can handle dynamic routes and receive the parameters like
notes/:id
. For that we just need to create a file named[id].js(x)
inside a notes folder. If the notes folder has an index file it will be treated as thenotes/
routeto use the param inside the render function we can use
useRouter
hook fromnext/router
. For classes, you havewithRouter
HOC.
notes/[id].js
import { useRouter } from 'next/router'
function App(){
const router = useRouter()
const {id} = router.query
return (
...
)
}
export default App
note: In React, functional components are actually the render function. The entire function is the render method in case of functional components. With classes, we will have an explicit render()
with a return value.
- if you want to handle slugs, like
notes/shihab/1
, you can have a file named[...slug].js
inside the notes directory. This time the router query will return an array-like['shihab',1]
. Even with catch-all routes, the index will still be used.
Navigation
Link component
For navigation next/link
expose a Link
element. It is always for client-side routing. That means, on navigation, this will not trigger a network call.
import Link from 'next/link'
function App(){
...
return {
....
<Link href='/notes/[id]' as={`/notes/${id}`}>
<a>Note</a>
</Link>
}
}
as
path will be the exact path URL, the href
will be the file's relative location. The href
prop takes a page name as it is in the pages directory. For dynamic routes, you will need the as
prop as well.
You must have an a tag as the child of the Link component, but the href lives on the Link.
For server-side routing, you can readily use an anchor tag like <a href='/docs'>Server side routing</a>
Programmatic routing
In order to navigate from code, one can use router.push()
from next/router
's useRouter
hook.
import { useRouter } from 'next/router'
function naviagteOnSuccess(){
const router = useRouter()
....
router.push('/notes/[id]',`/notes/${note.id}`)
}
Styling
if you are using global CSS,
pages/_app.js
is the only place you can import it. If you try to import it in other places Next.js will throw an error. This is more tied to the bundling of styles and loading themNext.js readily supports CSS Modules. With CSS modules we get file scoped styles. How it works is, with each import of CSS module file, a file specific class name gets added(prepended) to the classes you use. So the style you use is specific to that particular file and doesn't collide with others. The CSS modules will only work with non-pure selectors like classes and ids, etc and not with element selectors(div, span, p,...). The filename should be like
file-name.module.(s)css
.
Special files
_app.js
- if you want to hijack the entry file of Next,
_app.js
file is the place. If you want to inject global styles, props or anything, it should happen here. This_app.js
is automatically created for you out of the box if you don't.
Next.js config
-
next-config.js
in the root of the project
TS support
- Just create a
.tsconfig.json
in the root. - Next will ask you to add some libs and dependencies. Add them.
- Bhoom, now Next will auto-populate the tsconfig for you. No more traction in setting up TS.
API routes
- Next is a full-stack framework. You can have your API route handlers inside a directory
pages/api
. - The routing is the same as that for pages.
Data fetching
- by default fetch is available
Data can be fetched on the server and the client. Client-side data fetch is the same, what we do in a normal React app. The components may be rendered in the Server, but data fetching will only happen on the client in this case. That means, if you fetch the data in the client(using hooks or lifecycle methods), they aren't triggered on Server. The Server will render the view with the components initial State, that's all. No, waiting until the client fetches or manipulation is over.
To fetch data on the server we have
- getStaticProps
- getStaticPaths
- getServerSideProps
getInitialProps
All of the above methods are only meant to run on the server(except getInitialProps, during subsequent calls).
they are not even added to the client bundle
these methods can access DB, file system and all the things that can be done on the server-side
the return value(objects) of these methods are injected into or send to the client-side components as JSON files
getStaticProps
- to pass down any static props to the components, that are available during the build time
- it may receive the props from the getStaticPaths method
- the return value is always an object
- this object is available as the props inside the component
- when building dynamic pages you will have the
params
passed from getStaticPaths, inside the getStaticProps - it is only called once at the build time (when building the app using
next build
command)
export async function getStaticProps(context) {
return {
props: {}
}
}
getStaticPaths
- if you want to generate static pages you can use this method
- it should return an array of
paths
- the pages are created for the paths at build time
- if the pages need some data to be fetched, we use the getStaticProps
- it might not be required to statically generate all the pages in advance, so you can opt for runtime SSR using
fallback: true
- by using fallback you can show some loaders if required when the page is being built
export async function getStaticPaths() {
// get all the paths for your posts from an API
// or file system
const results = await fetch('/api/posts')
const posts = await results.json()
// create the paths array
const paths = posts.map(post => ({params: {slug:
post.slug}}))
/*
[
{params: {slug: 'get-started-with-node'}},
{params: {slug: 'top-frameworks'}}
]
*/
return {paths}
}
export async function getStaticProps({ params }) {
const res = await fetch(`/api/post/${params.slug}`)
const post = await res.json()
return {
props: {post}
}
}
getServerSideProps
- called on every request on the server
- used if you want to do some data fetching for dynamic SSR routes
- you will have access to HTTP header, query params,req and res headers
- even if it is client-side navigation, this method is triggered on the server-side and data is sent down. This is actually an extra roundtrip 😢.
export async function getServerSideProps() {
const response = await fetch(`https://somedata.com`)
const data = await response.json()
return { props: { data } }
}
getInitialProps
- not recommended as per docs, but not yet deprecated 💪
- on Server-Side Rendering(SSR) pages it is run on the server and data is passed down as JSON
- for Client-Side Rendering(CSR) it runs on the client
- used to fetch data
Note: when the page is fetched upon URL/address bar navigation, it is SSR. On client side navigation it is CSR.
When to use what
Do you need data at runtime but don't need SSR? Use client-side data fetching.
Do you need data at runtime but do need SSR? Use getServerSideProps
Do you have pages that rely on data that is cachable and accessible at build time? Like from a CMS? Use getStaticProps
Do you have the same requirement as above but the pages have dynamic URL params? Use getStaticProps and getStaticPaths
Rendering modes
Basically 3 rendering modes
Static: pages are built at run time.
Server Side: page are built on each request and cached after the initial hit
Client-side: the rendering happens on the client. The server will not send the HTML markup string. By default, the pages are pre-rendered while using Next.js.
The type of rendering is chosen based on the data fetching strategy we choose(mostly). By default, the pages are pre-rendered by Next. Pre-rendering means, the server sends down an HTML markup string to the client. Once the request is received the client will try to make it interactive by injecting listeners and handlers (hydration).
By choosing the appropriate data fetching strategy we can decide the rendering mode for the app.
If your component works with
- DOM APIs
- only on client data, there is no point in server-side rendering them. We can opt-out of SSR by using,
const NoSSR=dynamic(()=>import('../component'),{
loading:()=><div>Loading.....</div>,
ssr:false
})
Here <NoSSR/>
will always be client rendered.
Deployment
By default, it requires a Node.js environment. By using next export
we can create a pure static build from our Next project and server it.
Posted on August 31, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.