Next.js: A Quick Guide. Chapter 2.

In this chapter, we will focus on layouts, navigation with highlighting active link, images and rendering.

Layouts

Layout define how our pages will be formatted. It is shared between every page in path and sub path. It can be overridden by defining a new layout in the folder. Layout is of course a file, named: layout. We need at least one layout in root folder of application. Usually it contains header, content placement and footer. It is good place to have navigation of application as it will be constant to every page, that layout is applied.

Minimal root layout would be looking like this.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Layouts inside sub folders are nested. Which means, child layouts are wrapped by children properties. Notice that only root layout can contain <html> and <body> tags.

Templates

Another handy file is template. It is very similar to layout, but it persists across routes and handle state. Template can use useEffect and useState. The simplest template would look like:

export default function Template({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

Final code, including layout, will be resolved to this.

<Layout>
  {/* Note that the template is given a unique key. */}
  <Template key={routeParam}>{children}</Template>
</Layout>
Navigation

To keep navigation simple, next.js defines component <Link>. It requires href parameter. Using dynamic linking, we can easily create dynamic links, like:

<Link href={`/blog/${post.id}`}>{post.title}</Link>

Not every time, we can use Link component to navigate. When we need to change route from code, we can use Router. It is simple useRouter hook.

It can navigate in several differnet ways.

const router = useRouter();
router.push("/");    // add to browser stack and redirect
router.replace("/"); // not use history browser stack
router.refresh();    // refresh from server
router.back();       // move back in browser stack
router.forward();    // move forward in browser stack

There is good to know sometimes, when we are in our routing through page. For example, when we are writing a navigation component, and we need to know if we should highlight the current page. For this, we have usePathname from “next/navigation“. To get parameters of navigation url, we should use useSearchParams. For sample address: http://localhost/post?id=1234, usePathname returns post, useSearchParams returns 1234. It works on client side.

For server side component, when we define our App Route as [id], we can extract it accordingly.

const Page = ({params, searchParams}) => {
    // params = { id: 'post' }
    // searchParams = { q: '1234' }
}
Images

Next.js defines a component <Image> to easily maintain images. This component requires several parameters to work correctly. The most important parameter is src. It defines, what will be displayes. It can be url, local path or base64 string.

When we are linking external urls, we need to whitelist them using next.config.js file.

images: {
    remotePatterns: [{
        protocol: 'https',
        hostname: 'address.com'
    }]
 }

Getting back to parameters. We need to define alt string. It is generally good practice to always set this prop even we are using simple <img> tag.

To proper display image, there is needed to define width and/or height. Those two are required. There is an exception to that rule. When we define fill={true} property for statically imported image, we can skip those two params. We can also skip them, if we have wrapping div with defined sizes and position style is one of: relative, fixed or absolute, and fill={true}.

<div className="w-full h-64 relative">
    <Image src={post.image} fill={true} alt="image" />
</div>
Rendering

Rendering converts code to HTML. This process could be at server side or client side. Both have pros and cons. I will not go into details, because it is good enough explained in documentation. I would like to share the most important thing about both methods and summary when use which method.

Server side rendering

When a user is sending a request for a page, the browser sends the request to the server. The server renders the static HTML page, adds interactive JavaScript and sends it back to the browser.

Pros:

  • Initial load is faster. Servers are usually better machines then users.
  • SEO. Rendered static HTML pages are good content to search engines and crawlers.
  • Data Fetching is at server side, we do not need to send multiple requests for data, we just send one and get whole page.
  • Security. Everything is done at server side. This means no queries for are sent to the server. We minimize security breaches like sending tokens, API keys etc.

Cons:

  • Less interaction. Static page can have minimal user interaction. Every React hook need to be client side.
  • Server load. As our service grows, multiple users are starting to load the server. This requires a more powerful machine, which is usually costly.
  • State management is complex.
Client side rendering

When a user is sending a request for a page, the browser sends the request to the server. The server sends an empty HTML page and whole JavaScript application. Next, the browser is rendering the page to finally who it to the user.

Pros:

  • Better performance after initial load. No need to query every page and wait for a server to render it. After load application code, it is purely local and as fast as local machine.
  • Less server load.
  • Best for user interactivity. As everything is local, every user action can be dynamic.
  • This is a only way to access browser API like localStorage or geolocating.

Cons:

  • Affects SEO. Search engines do not like pages without initial HTML content.
  • Slower initial load. As whole, rendering is done at client side.
  • Depends on client resources. Slower machines like smartphones can be less responsive than a powerful desktop when our application is big.

Next.js can take advantages from both worlds. You can have one component, which is server side. It can contain other component at client side and this component can have third component which is server side. This provides us every pros from both worlds and minimize cons.

Client side component:

"use client"
const ClientSideComponent = ({children}) => {
    return (
        <div>
            {children}
        </div>
    )
}
export default ClientSideComponent

Server side component:

<ClientSideComponent>
    <div>server data</div>
</ClientSideComponent>

In the next chapter, I will write about fetching data in two ways: Server Action and API. Stay tuned.

The whole example project can be found here.