Nextjs - the best framework for React

June 29, 2022

If React is a UI/state library, then Next.js shows us how to use the library the right way to build fast web applications and streamline the development process. This post introduces Next.js, highlighting some of its key features with examples that demonstrate how easy it is to use Next.js with React.

Simplified Routing

One of the fundamental aspects of web apps is routing. In many React apps I worked on before Next.js, developers had to freestyle their project's folder structure and naming conventions, which led to inconsistencies and readability issues. Every project had a slightly different file structure, making it hard to follow.

Next.js automatically handles routing for you. Simply create files under the /pages folder and export them as default.

    pages/
    |---> index.js
    |---> about.js
    |---> dashboard/
          |---> index.js
          |---> users.js

Then your site will be accessible at

    /
    /about
    /dashboard
    /dashboard/users

Dynamic Routes

Next.js supports dynamic page routing. For example, if you create a file structure like this:

    pages/
    |---> users/
          |---> [userId].js

Your site will be accessible at pages/users/1, pages/users/2, etc. The matched parameters will be sent as query parameters (userId in this case) to the page.

    import {useRouter} from 'next/router'

    export default () => {
        const { userId } = useRouter().query
        return ...
    }

Per-Page Layouts

Sometimes, multiple pages share a common layout. It's a good practice to separate the layout components to avoid repetition. For example, let's say we have two pages that share the same layout:

/page-a

Side Bar

Page A

/page-b

Side Bar

Page B

Create DashboardLayout

import SideBar from '../components/SideBar'

export default DashboardLayout({ content }){
    return <div style={{display:"grid", gridTemplateColumns:"20% 1fr"}}>
        <SideBar/>
        {content}
    </div>
}

Adding property getLayout to your pages.

import DashboardLayout from '../layouts/dashboard-layout'

export default function PageA() {
    return <div>
        PageA
    </div>
}

PageA.getLayout = (page) => {
  return (
    <DashboardLayout>
        {page}
    </DashboardLayout>
  )
}
import DashboardLayout from '../layouts/dashboard-layout'

export default function PageB() {
    return <div>
        PageB
    </div>
}

PageB.getLayout = (page) => {
  return (
    <DashboardLayout>
        {page}
    </DashboardLayout>
  )
}

When using Next.js you’ll most likely need to override the global App component to get access to some features like persisting state, global css or layouts. This can be done by creating a file _app.js directly under the pages/ folder. In your pages/_app.js

export default function MyApp({ Component, pageProps }) {
    // Use the layout defined at the page level, if available
    const getLayout = Component.getLayout || ((page) => page)
    return <>
        {getLayout(<Component {...pageProps} />)}
    </>
}

This layout pattern enables state persistence because the React component tree is maintained between page transitions. For more read on official docs

getServerSideProps

If you export an async function called getServerSideProps from a page, Nextjs will pre-render this page on each request using the data returned by getServerSideProps.

export async function getServerSideProps(context) {
    const { params, locale} = context;
    const res = await fetchUserDataById(params.id) // fetch data from database
    return {
        props: {
            userData: res?.data
            locale
        }, // will be passed to the page component as props
    }
}

export default function User(props){ 
    const { userData, locale } = props; // <-- props received from getServerSideProps
    return <>
        ...
    </>
}

The context parameter is an object containing the following keys:

  • params: If this page uses a dynamic route, params contains the route parameters. If the page name is [id].js , then params will look like { id: ... }.
  • req: The HTTP IncomingMessage object, with an additional cookies prop, which is an object with string keys mapping to string values of cookies.
  • res: The HTTP response object.
  • query: An object representing the query string.
  • preview: preview is true if the page is in the Preview Mode and false otherwise.
  • previewData: The preview data set by setPreviewData.
  • resolvedUrl: A normalized version of the request URL that strips the _next/data prefix for client transitions and includes original query values.
  • locale contains the active locale (if enabled).
  • locales contains all supported locales (if enabled).
  • defaultLocale contains the configured default locale (if enabled).

getStaticProps

Nextjs will pre-render this page at build time using the data returned by getServerSideProps.

export async function getStaticProps() {
    const { params, locale} = context;
    // Call an external API endpoint to get data
    const userData = await await fetch(`https://.../user/${params.id}`).then(res => res.json()); 
    return {
        props: {
            userData,
            locale
        }, // will be passed to the page component as props
    }
}

export default function User(props){ 
    const { userData, locale } = props; // <-- props received from getServerSideProps
    return <>
        ...
    </>
}

The context parameter is an object containing the following keys:

  • params contains the route parameters for pages using dynamic routes. For example, if the page name is [id].js , then params will look like { id: ... }. You should use this together with getStaticPaths, which we’ll explain later.
  • preview is true if the page is in the Preview Mode and undefined otherwise.
  • previewData contains the preview data set by setPreviewData.
  • locale contains the active locale (if enabled).
  • locales contains all supported locales (if enabled).
  • defaultLocale contains the configured default locale (if enabled).

Wait, it is doing the same as getServerSideProps ? Yes, both functions passed some props to the page, but there are few differences to consider.

getServerSideProps

  • This function get called every time you visit the site, the server will have to build the page every time each request.
  • Can be slow
  • Great for dynamic data that changes regularly

getStaticProps

  • This function get called only once at build time. Props remain the same until the next build.
  • Great for speed and SEO
  • Great for static data

getStaticPaths

Assume you are building a blog site. You want to pre-render your articles as static pages for fast serving and better SEO.

export async function getStaticPaths() {
  // Read the files inside the pages/posts dir
  const articles = await fetch('https://.../articles').then(res => res.json());
    //   articles = [
    //     {
    //         id:"1",
    //         title:"hello-world"
    //     },
    //     {
    //         id:"2",
    //         title:"data-fetching-in-react"
    //     }
    //   ]

  // Generate path for each file
  const paths = articles.map((article) => {
    return {
      params: {
        slug: article.id
      },
    };
  });

  return {
    paths,
    fallback: false,
  };
}

The value for each params object must match the parameters used in the page name. If the page name is articles/[slug].js, then params should contain slug.

This function run once at build time and can be used altogether with getStaticProps but can not be used with getServerSideProps. For more read on official docs

export default function Post({ post }) {
  // Render post...
}

// This function gets called at build time
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const posts = await fetch('https://.../posts').then(res => res.json())

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false }
}

// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const post = await fetch(`https://.../posts/${params.id}`).then(res => res.json())

  // Pass post data to the page via props
  return { props: { post } }
}

Conclusion

Next.js is an exceptional framework for React. If you're a React developer, you should definitely explore Next.js. With its powerful features and tools, Next.js can save you time and simplify your development process. Give it a try!