Next js Tutorial

Next js Tutorial

Next js Tutorial

1. Introduction to Next.js

1.1 What is Next.js?

Next.js is a React framework that allows developers to build powerful web applications with minimal configuration. It is created by Vercel and is often used for building production-ready apps with built-in features such as Server-Side Rendering (SSR), Static Site Generation (SSG), and API Routes. It also comes with preconfigured performance optimizations and SEO enhancements.

1.2 Advantages of Using Next.js

  1. Server-Side Rendering (SSR): Next.js allows developers to render pages on the server, improving SEO and load times for content-heavy websites.
  2. Static Site Generation (SSG): With Next.js, you can pre-generate pages at build time, which results in fast page loads and reduced server costs.
  3. Hybrid Rendering: You can use both SSR and SSG in the same application, making Next.js extremely versatile for different use cases.
  4. Built-in Routing: File-based routing in Next.js simplifies route management by automatically generating routes based on the file structure.
  5. API Routes: You can create RESTful APIs within the same application using Next.js API routes.
  6. Performance Optimizations: Next.js optimizes your application by default, with features like code splitting, lazy loading, and image optimization.
  7. SEO-Friendly: Unlike client-side rendering in React, Next.js pages are rendered on the server, allowing search engines to easily crawl and index content.
  8. Vercel Deployment: Next.js is built by Vercel, making the deployment process on Vercel seamless and optimized for performance.

1.3 How Next.js Differs from React

  • React is a JavaScript library for building UI components, but it does not provide tools for routing, API handling, or SSR/SSG. These features must be implemented using third-party tools like React Router or Axios.
  • Next.js is a framework built on top of React that handles routing, SSR, SSG, and API routes, providing a more complete solution out of the box.
FeatureReactNext.js
RenderingClient-SideSSR, SSG, CSR
RoutingReact RouterBuilt-in File-Based
API IntegrationExternalBuilt-in API Routes
SEOLimitedExcellent (due to SSR/SSG)

1.4 Installation and Setup of a Next.js Project

Let’s get started with creating a Next.js app.

Prerequisites:
  • Node.js installed on your machine (preferably the latest LTS version).
  • npm or yarn package manager.
Steps to Install Next.js:
  1. Create a New Project: You can create a Next.js app by running:
   npx create-next-app@latest my-next-app
   
    //Next js Tutorial

Alternatively, if you use yarn:

   yarn create next-app my-next-app
   
    //Next js Tutorial

This will set up a Next.js project with the necessary dependencies and a basic folder structure.

  1. Navigate to Your Project:
   cd my-next-app
    
    //Next js Tutorial
  1. Run the Development Server:
   npm run dev
    
    //Next js Tutorial

Or for yarn:

   yarn dev
    
    //Next js Tutorial

You can now open your browser and visit http://localhost:3000 to see your Next.js app in action.

1.5 Folder Structure

When you create a Next.js project, you will see the following structure:

my-next-app/
├── pages/
│   ├── index.js
│   └── _app.js
├── public/
├── styles/
├── .next/
├── package.json
└── next.config.js

 
    //Next js Tutorial
  • pages/: Contains the React components for your pages. Files in this folder automatically become routes.
  • public/: For static assets like images and fonts.
  • styles/: Stores global and modular CSS files.
  • .next/: Automatically generated folder containing the output of your Next.js build.
  • next.config.js: Configuration for your Next.js app.

1.6 Conclusion

Next.js provides a powerful, all-in-one solution for building fast, SEO-friendly, and scalable web applications. With its built-in routing, SSR/SSG capabilities, and performance optimizations, it’s an excellent choice for developers looking to go beyond basic React setups.

2. File-Based Routing in Next.js

In Next.js, routing is based on the file and folder structure inside the pages directory. Unlike traditional React apps where routing is done via react-router, Next.js automatically generates routes based on the filenames within the pages folder. This approach simplifies the routing logic, making it more intuitive and reducing the need for extra configuration.

2.1 Basic Routing

Every file you add to the pages/ directory automatically becomes a route.

Example:
  • File: pages/index.js
  • Route: / (home page)
  • File: pages/about.js
  • Route: /about
// pages/index.js
export default function Home() {
  return <h1>Welcome to the Home Page</h1>;
}

// pages/about.js
export default function About() {
  return <h1>About Us</h1>;
}

 
    //Next js Tutorial
Folder Structure Example:
pages/
├── index.js  // Maps to "/"
├── about.js  // Maps to "/about"
 
    
//Next js Tutorial

2.2 Dynamic Routes

Dynamic routing in Next.js is done by adding square brackets around a filename, indicating a dynamic segment of the URL.

Example:
  • File: pages/blog/[id].js
  • Route: /blog/1, /blog/2, etc.
// pages/blog/[id].js
import { useRouter } from 'next/router';

export default function BlogPost() {
  const router = useRouter();
  const { id } = router.query;

  return <h1>Blog Post {id}</h1>;
}

//Next js Tutorial
Explanation:
  • [id].js creates a dynamic route where id is a parameter.
  • Access the dynamic value using useRouter and router.query.
Nested Dynamic Routes:

You can nest dynamic routes for complex paths.

  • File: pages/user/[username]/posts/[postId].js
  • Route: /user/john/posts/5
// pages/user/[username]/posts/[postId].js
import { useRouter } from 'next/router';

export default function UserPost() {
  const router = useRouter();
  const { username, postId } = router.query;

  return (
    <div>
      <h1>Post {postId} by {username}</h1>
    </div>
  );
}

//Next js Tutorial

2.3 Catch-All Routes

You can create a catch-all route by using [[...param]]. This will match any number of segments and can even work when no segment is provided.

Example:
  • File: pages/docs/[[...slug]].js
  • Route: /docs, /docs/installation, /docs/guides/basic-setup
// pages/docs/[[...slug]].js
import { useRouter } from 'next/router';

export default function Docs() {
  const router = useRouter();
  const { slug } = router.query;

  if (!slug) {
    return <h1>Docs Home</h1>;
  }

  return <h1>Docs Section: {slug.join('/')}</h1>;
}

//Next js Tutorial
Explanation:
  • [[...slug]].js handles multiple route segments.
  • If the route is /docs/installation/setup, slug will be an array: ['installation', 'setup'].

2.4 Linking Between Pages

In Next.js, you can navigate between pages using the Link component from next/link.

Example:
import Link from 'next/link';

export default function Home() {
  return (
    <div>
      <h1>Welcome to the Home Page</h1>
      <Link href="/about">
        <a>Go to About Page</a>
      </Link>
    </div>
  );
}

//Next js Tutorial

The Link component is used to prevent a full page reload and to handle client-side navigation, improving performance and user experience.

2.5 Nested Routes

You can create nested routes by organizing files inside folders.

Example:
  • Folder: pages/blog/
  • File: pages/blog/index.js/blog
  • File: pages/blog/new.js/blog/new
// pages/blog/index.js
export default function BlogHome() {
  return <h1>Blog Home</h1>;
}

// pages/blog/new.js
export default function NewPost() {
  return <h1>Create a New Blog Post</h1>;
}

//Next js Tutorial
Folder Structure:
pages/
└── blog/
    ├── index.js  // Maps to "/blog"
    ├── new.js    // Maps to "/blog/new"

2.6 API Routes

In addition to page routing, Next.js allows you to create API routes. These routes are defined inside the pages/api/ folder and can handle requests like a traditional backend API.

Example:
  • File: pages/api/hello.js
  • Route: /api/hello
// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello, World!' });
}

//Next js Tutorial
  • This API route can be accessed via a GET request at /api/hello.
  • API routes can handle various HTTP methods (GET, POST, etc.), making it easier to build backend functionality without additional tools.

2.7 Route Prefetching

Next.js automatically prefetches pages in the background when a Link component is visible in the viewport. This results in faster page transitions.

You can disable prefetching by adding the prefetch={false} prop to the Link component.

Example:
<Link href="/about" prefetch={false}>
  <a>About Us</a>
</Link>

//Next js Tutorial

2.8 Custom Error Pages

You can create custom error pages for 404 or 500 errors by adding special files to the pages folder.

  • File: pages/404.js → Custom 404 Page
  • File: pages/500.js → Custom 500 Page
// pages/404.js
export default function Custom404() {
  return <h1>404 - Page Not Found</h1>;
}

//Next js Tutorial

2.9 Conclusion

Next.js simplifies routing by using a file-based system, eliminating the need for extra routing libraries. Dynamic and nested routes, along with the ability to create API routes within the same project, make Next.js a powerful and efficient tool for building modern web applications.

3. Pages and Layouts in Next.js

Next.js provides a clean and efficient way to manage pages and layouts in your application. With its file-based routing system, each file inside the pages directory automatically becomes a route, and you can customize layouts for each page or apply a global layout for your entire app.

3.1 Creating Pages

Every file you create in the pages directory becomes a page in your application. Pages in Next.js are essentially React components that correspond to specific routes.

Example:
  • File: pages/index.js (home page)
  • Route: /
// pages/index.js
export default function Home() {
  return <h1>Welcome to the Home Page</h1>;
}

//Next js Tutorial
  • File: pages/about.js
  • Route: /about
// pages/about.js
export default function About() {
  return <h1>About Us</h1>;
}

//Next js Tutorial

The structure of the pages in the pages/ directory directly maps to the routes in the app.

3.2 Special Pages (_app.js and _document.js)

Next.js uses two special pages, _app.js and _document.js, for global settings and customizing HTML structure.

3.2.1 _app.js (Custom App Component)

The _app.js file allows you to customize the behavior of your entire application. It wraps around every page component and can be used to set up global CSS, persistent layouts, or any context providers (e.g., Redux, Theme providers).

Example:
// pages/_app.js
import '../styles/globals.css';

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

//Next js Tutorial
  • The Component prop represents the active page being rendered.
  • pageProps contains the initial props that were preloaded.

If you need a layout that should be persistent across all pages (e.g., a navigation bar or footer), you can include it here.

// pages/_app.js
import Layout from '../components/Layout';
import '../styles/globals.css';

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

//Next js Tutorial
3.2.2 _document.js (Custom Document)

The _document.js file is used to customize the HTML document that is rendered on the server side. It’s only rendered once, and it wraps your application’s HTML structure.

Example:
// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document';

export default function MyDocument() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

//Next js Tutorial
  • <Html>: Represents the entire HTML document.
  • <Head>: Used to add meta tags, external stylesheets, and other head elements.
  • <Main>: Contains the main content of your application.
  • <NextScript>: Includes Next.js scripts required for client-side functionality.

3.3 Layout Components

A layout is a shared component (e.g., header, sidebar, footer) that can be reused across multiple pages. You can create a layout by defining a custom React component and wrapping it around your page components.

Example:
// components/Layout.js
import Navbar from './Navbar';
import Footer from './Footer';

export default function Layout({ children }) {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  );
}

//Next js Tutorial

You can then apply this layout globally by wrapping it in the _app.js file as shown earlier, or apply it to specific pages.

Example for Applying to a Single Page:
// pages/about.js
import Layout from '../components/Layout';

export default function About() {
  return (
    <Layout>
      <h1>About Us</h1>
    </Layout>
  );
}

//Next js Tutorial

3.4 Persistent Layouts

In some cases, you may want to create layouts that persist across page navigations without re-rendering, such as when using a sidebar or navigation menu.

You can achieve this by creating a layout wrapper in _app.js or using a custom layout for individual pages.

Dynamic Layout per Page:

You can define a layout for each page by adding a getLayout function to the page component. This gives you flexibility to switch between different layouts for different pages.

Example:
// components/MainLayout.js
export default function MainLayout({ children }) {
  return (
    <div>
      <header>Main Header</header>
      <main>{children}</main>
      <footer>Main Footer</footer>
    </div>
  );
}

// components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <header>Dashboard Header</header>
      <main>{children}</main>
    </div>
  );
}

// pages/about.js
import MainLayout from '../components/MainLayout';

export default function About() {
  return <h1>About Us</h1>;
}

About.getLayout = function getLayout(page) {
  return <MainLayout>{page}</MainLayout>;
};

// pages/dashboard.js
import DashboardLayout from '../components/DashboardLayout';

export default function Dashboard() {
  return <h1>Dashboard</h1>;
}

Dashboard.getLayout = function getLayout(page) {
  return <DashboardLayout>{page}</DashboardLayout>;
};

//Next js Tutorial

You can now update _app.js to handle the layouts dynamically:

// pages/_app.js
export default function MyApp({ Component, pageProps }) {
  const getLayout = Component.getLayout || ((page) => page);

  return getLayout(<Component {...pageProps} />);
}

//Next js Tutorial

This way, different pages can have different layouts, and the layouts will persist across navigations.

3.5 Using the Head Component

The Head component provided by Next.js allows you to modify the <head> section of your HTML document. You can add meta tags, link tags for external stylesheets, or set the title of the page dynamically.

Example:
import Head from 'next/head';

export default function About() {
  return (
    <>
      <Head>
        <title>About Us - MyWebsite</title>
        <meta name="description" content="Learn more about us." />
      </Head>
      <h1>About Us</h1>
    </>
  );
}

//Next js Tutorial

3.6 SEO with Layouts

One of the advantages of Next.js is that it improves SEO by default due to server-side rendering (SSR) and static generation (SSG). However, you can further enhance SEO by using the Head component to include:

  • Meta descriptions (<meta> tags)
  • Canonical URLs
  • Open Graph tags for sharing on social media
  • Twitter Cards for Twitter integration

3.7 Conclusion

In Next.js, page management is simple with the file-based routing system, and layouts can be applied globally or per page. By using _app.js, _document.js, and custom layouts, you can efficiently structure your application, manage global styles, and implement SEO best practices.

4. Rendering Methods in Next.js

Next.js provides multiple rendering methods to suit different types of applications. These methods determine how and when content is generated and delivered to the client. The primary rendering methods in Next.js are:

  1. Server-Side Rendering (SSR)
  2. Static Site Generation (SSG)
  3. Client-Side Rendering (CSR)
  4. Incremental Static Regeneration (ISR)

Each method has its own use cases, performance characteristics, and SEO implications.

4.1 Server-Side Rendering (SSR)

Server-Side Rendering (SSR) is the process of generating HTML on the server for each request. In this method, the page is rendered on the server and sent to the browser with all the necessary HTML. This improves SEO and performance for pages with dynamic content.

How it works:
  • The page is rendered on the server at request time.
  • Next.js fetches data from APIs or databases on the server side, compiles the page, and sends the fully rendered HTML to the client.
When to use SSR:
  • When content changes frequently and needs to be updated on every request.
  • For pages that rely on user-specific data (e.g., personalized dashboards).
  • For SEO-sensitive pages with dynamic content.
Example of SSR using getServerSideProps:
// pages/blog/[id].js
export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`https://api.example.com/blog/${id}`);
  const post = await res.json();

  return {
    props: {
      post,
    },
  };
}

export default function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

//Next js Tutorial
  • getServerSideProps fetches data on every request and passes it to the component as props.
  • This method ensures that content is always up-to-date but may increase server load due to frequent rendering.
Key Points:
  • SEO: Great for SEO because pages are fully rendered on the server.
  • Performance: May be slower than static methods since it renders content for every request.
  • Data freshness: Provides the most up-to-date content.

4.2 Static Site Generation (SSG)

Static Site Generation (SSG) generates the HTML of a page at build time, meaning the page is compiled into static files when the application is built and deployed. These static files are served to the client instantly, offering extremely fast performance.

How it works:
  • Pages are pre-rendered at build time and served as static files.
  • Any data needed for the page is fetched during the build process.
When to use SSG:
  • For pages that don’t change frequently (e.g., marketing pages, blog posts).
  • When you want blazing-fast performance since the content is served statically.
  • When SEO is important but content doesn’t need to change on every request.
Example of SSG using getStaticProps:
// pages/blog/[id].js
export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.example.com/blog/${params.id}`);
  const post = await res.json();

  return {
    props: {
      post,
    },
  };
}

export async function getStaticPaths() {
  const res = await fetch('https://api.example.com/blog');
  const posts = await res.json();

  const paths = posts.map((post) => ({
    params: { id: post.id.toString() },
  }));

  return {
    paths,
    fallback: false,
  };
}

export default function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

//Next js Tutorial
  • getStaticProps runs at build time and fetches the data required to render the page.
  • getStaticPaths pre-generates the paths for all dynamic pages (e.g., blog post IDs).
Key Points:
  • SEO: Excellent for SEO because the pages are pre-rendered.
  • Performance: Pages load quickly as they are served from static files.
  • Data freshness: Data is as fresh as the last build. For frequently updated content, you may need to rebuild the site.

4.3 Client-Side Rendering (CSR)

In Client-Side Rendering (CSR), pages are rendered entirely in the browser using JavaScript. This method is similar to how a typical React app works, where the client fetches data after the initial page load.

How it works:
  • The initial HTML sent to the browser contains minimal content (usually just a loading spinner).
  • Once the page loads, JavaScript runs and fetches data, rendering the final content in the client.
When to use CSR:
  • For highly interactive pages that rely on dynamic data (e.g., dashboards, social media feeds).
  • When SEO is not critical.
  • When the data is too dynamic to be pre-rendered on the server.
Example using useEffect for CSR:
import { useEffect, useState } from 'react';

export default function BlogPost({ id }) {
  const [post, setPost] = useState(null);

  useEffect(() => {
    async function fetchPost() {
      const res = await fetch(`/api/blog/${id}`);
      const data = await res.json();
      setPost(data);
    }
    fetchPost();
  }, [id]);

  if (!post) return <p>Loading...</p>;

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

//Next js Tutorial
Key Points:
  • SEO: Not ideal for SEO because the content is not available until the client renders it.
  • Performance: Initial load may be slower, but subsequent page transitions can be very fast.
  • Data freshness: Always the freshest since data is fetched at runtime in the browser.

4.4 Incremental Static Regeneration (ISR)

Incremental Static Regeneration (ISR) allows you to use static generation with the benefit of updating static pages after they’ve been deployed. This means you don’t have to rebuild your entire site to update content; instead, you can update specific pages incrementally.

How it works:
  • Static pages are generated at build time.
  • After the initial build, the static pages are regenerated in the background at a specified interval (or when new content is available).
When to use ISR:
  • When you want the performance benefits of static pages but need to update content regularly.
  • For e-commerce, blog posts, or other dynamic content that changes periodically but doesn’t need real-time updates.
Example using revalidate with ISR:
// pages/blog/[id].js
export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.example.com/blog/${params.id}`);
  const post = await res.json();

  return {
    props: {
      post,
    },
    revalidate: 60, // Revalidate the page every 60 seconds
  };
}

export async function getStaticPaths() {
  const res = await fetch('https://api.example.com/blog');
  const posts = await res.json();

  const paths = posts.map((post) => ({
    params: { id: post.id.toString() },
  }));

  return {
    paths,
    fallback: 'blocking',
  };
}

export default function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

//Next js Tutorial
  • The revalidate field determines how often the page should be re-generated.
  • Pages are updated in the background, and users always see the latest cached version.
Key Points:
  • SEO: Great for SEO since pages are still pre-rendered.
  • Performance: Offers static performance but with fresher data.
  • Data freshness: Data is updated based on the revalidation interval.

4.5 Comparison of Rendering Methods

MethodRendering TimeSEOUse CasesPerformance
SSROn every requestHighDynamic content, personalized pagesSlower due to server load
SSGAt build timeHighStatic content, blogs, landing pagesFast (static files)
CSRClient-sideLowInteractive, highly dynamic pagesSlower initial load
ISRBuild + incrementalHighE-commerce, frequently updated blogsFast with data freshness

4.6 Conclusion

Next.js provides flexibility with multiple rendering methods, allowing developers to choose the most suitable approach depending on the application’s needs. Whether you need high performance, up-to-date data, or SEO, Next.js offers the tools to balance these priorities. By understanding how SSR, SSG, CSR, and ISR work, you can optimize your application for performance, scalability, and user experience.

5. Styling in Next.js

Next.js offers a variety of ways to style applications, providing developers with flexibility to choose what suits their project. You can use CSS, Sass, CSS-in-JS libraries, or even global CSS for styling components and pages. In this section, we’ll explore the common approaches to styling in Next.js:

  1. Global CSS
  2. CSS Modules
  3. Sass
  4. Styled JSX (CSS-in-JS)
  5. Third-Party CSS-in-JS Libraries
  6. Tailwind CSS (Utility-First Framework)

5.1 Global CSS

Global CSS in Next.js allows you to define styles that apply across your entire application. Global styles can be imported in the _app.js file, which wraps all your pages.

How to Use Global CSS:
  1. Create a global CSS file inside a styles/ folder (e.g., styles/globals.css).
  2. Import the CSS file in _app.js.
Example:
/* styles/globals.css */
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f5f5f5;
}

//Next js Tutorial
// pages/_app.js
import '../styles/globals.css';

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

//Next js Tutorial
  • Key Points:
  • Global CSS is useful for setting up general styles like typography, background colors, or resets.
  • Only one global CSS import is allowed in _app.js (e.g., you can’t import CSS in individual pages).

5.2 CSS Modules

CSS Modules offer a way to scope CSS locally to a specific component or page. This prevents naming conflicts and makes your styles more maintainable.

How to Use CSS Modules:
  1. Create a CSS file with the .module.css extension.
  2. Import the CSS file directly into your component or page.
Example:
/* styles/Home.module.css */
.container {
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
}
.title {
  font-size: 2rem;
  color: #0070f3;
}

//Next js Tutorial
// pages/index.js
import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <h1 className={styles.title}>Welcome to Next.js</h1>
    </div>
  );
}

//Next js Tutorial
  • Key Points:
  • CSS Modules generate unique class names, so styles are scoped locally.
  • You can use CSS Modules for component-level styling, making your codebase more modular.

5.3 Sass (SCSS)

Next.js has built-in support for Sass (SCSS), which allows you to use variables, nesting, and other advanced features in your CSS.

How to Use Sass:
  1. Install sass in your Next.js project:
   npm install sass
   
//Next js Tutorial
  1. Create .scss or .sass files in the styles/ directory.
  2. Import these files in your components or globally in _app.js.
Example:
/* styles/Home.module.scss */
$primary-color: #0070f3;

.container {
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;

  .title {
    font-size: 2rem;
    color: $primary-color;
  }
}

//Next js Tutorial
// pages/index.js
import styles from '../styles/Home.module.scss';

export default function Home() {
  return (
    <div className={styles.container}>
      <h1 className={styles.title}>Welcome to Next.js with Sass</h1>
    </div>
  );
}
  • Key Points:
  • Sass offers more flexibility and power over regular CSS, allowing for things like mixins, nesting, and variables.
  • You can combine Sass with CSS Modules for component-scoped Sass files.

5.4 Styled JSX (CSS-in-JS)

Styled JSX is the built-in CSS-in-JS solution provided by Next.js. It allows you to write scoped styles directly inside your components without needing external CSS files.

How to Use Styled JSX:
  1. Use the <style jsx> tag inside your component to define styles.
  2. The styles defined inside <style jsx> are scoped to that component.
Example:
export default function Home() {
  return (
    <div className="container">
      <h1 className="title">Welcome to Styled JSX</h1>

      <style jsx>{`
        .container {
          padding: 20px;
          background-color: #fff;
          border-radius: 8px;
        }
        .title {
          font-size: 2rem;
          color: #0070f3;
        }
      `}</style>
    </div>
  );
}

//Next js Tutorial
  • Key Points:
  • Styled JSX styles are automatically scoped to the component.
  • No need to worry about class name collisions.
  • Supports dynamic styles based on component props or state.

5.5 Third-Party CSS-in-JS Libraries (Styled Components & Emotion)

You can also use third-party CSS-in-JS libraries like Styled Components and Emotion in Next.js. These libraries provide advanced styling features, such as theming, server-side rendering support, and more.

How to Use Styled Components:
  1. Install styled-components:
   npm install styled-components
  1. Install babel-plugin-styled-components for better server-side rendering support.
  2. Create a babel.config.js file in your project and add:
   module.exports = {
     presets: ["next/babel"],
     plugins: [["styled-components", { "ssr": true }]],
   };
   
//Next js Tutorial
  1. Write your styled components:
import styled from 'styled-components';

const Container = styled.div`
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
`;

const Title = styled.h1`
  font-size: 2rem;
  color: #0070f3;
`;

export default function Home() {
  return (
    <Container>
      <Title>Welcome to Styled Components</Title>
    </Container>
  );
}

//Next js Tutorial
  • Key Points:
  • Styled Components and Emotion provide more advanced features like theming, server-side rendering, and extending styles.
  • They are popular choices for CSS-in-JS solutions in Next.js apps.

5.6 Tailwind CSS (Utility-First Framework)

Tailwind CSS is a utility-first CSS framework that allows you to build custom designs quickly using predefined utility classes.

How to Use Tailwind CSS:
  1. Install Tailwind CSS:
   npm install -D tailwindcss postcss autoprefixer
   npx tailwindcss init
   
//Next js Tutorial
  1. Configure tailwind.config.js and create the necessary files.
  2. Add Tailwind’s directives to your global CSS file (e.g., globals.css):
@tailwind base;
@tailwind components;
@tailwind utilities;

//Next js Tutorial
  1. Use Tailwind’s utility classes directly in your components.
Example:
export default function Home() {
  return (
    <div className="p-6 bg-white rounded-lg">
      <h1 className="text-2xl text-blue-600">Welcome to Tailwind CSS</h1>
    </div>
  );
}

//Next js Tutorial
  • Key Points:
  • Tailwind CSS allows for rapid UI development by combining multiple classes.
  • It’s highly customizable and eliminates the need for writing custom CSS for every component.

5.7 Choosing the Right Styling Method

The right styling method depends on the size and complexity of your project:

  • Global CSS: Best for general, application-wide styles (e.g., resets, typography).
  • CSS Modules: Ideal for component-scoped, modular styles.
  • Sass: Great if you prefer SCSS syntax or need more advanced CSS features.
  • Styled JSX: Useful for writing scoped styles directly inside components.
  • CSS-in-JS Libraries: If you need advanced theming, server-side rendering, or dynamic styling.
  • Tailwind CSS: For fast development using a utility-first approach, particularly when designing custom UIs.

5.8 Conclusion

Next.js gives you multiple options for styling your application, from traditional CSS to modern CSS-in-JS approaches. Whether you prefer utility-first frameworks like Tailwind or CSS-in-JS libraries like Styled Components, Next.js offers the flexibility to integrate your preferred method while ensuring maintainability, performance, and scalability.

6. API Routes in Next.js

Next.js allows you to create API routes to build backend functionality directly within your Next.js application. These routes are server-side functions that can handle HTTP requests and provide responses, making it easy to integrate APIs, handle form submissions, perform CRUD operations, and more.

6.1 What Are API Routes?

API Routes in Next.js let you create a RESTful API directly in your app. These routes are stored in the /pages/api/ directory, and each file in this directory corresponds to a distinct API endpoint.

  • For example, /pages/api/hello.js becomes an endpoint at /api/hello.
  • API routes can handle all types of HTTP methods: GET, POST, PUT, DELETE, etc.

6.2 Setting Up API Routes

To create an API route, add a JavaScript or TypeScript file to the pages/api/ folder. Each file exports a default function that handles incoming HTTP requests and sends responses.

Example:
// pages/api/hello.js

export default function handler(req, res) {
  res.status(200).json({ message: 'Hello, Next.js!' });
}

//Next js Tutorial
  • req: The incoming request object, which contains information about the HTTP request (e.g., headers, method, body).
  • res: The response object, which is used to send back data to the client.

When you visit /api/hello, you will receive the JSON response:

{
  "message": "Hello, Next.js!"
}

//Next js Tutorial

6.3 Handling HTTP Methods

You can handle different HTTP methods like GET, POST, PUT, and DELETE within the same API route. By checking req.method, you can differentiate between the types of requests and handle them accordingly.

Example: Handling Multiple HTTP Methods
// pages/api/users.js

export default function handler(req, res) {
  const { method } = req;

  switch (method) {
    case 'GET':
      // Handle GET request - fetch users
      res.status(200).json({ users: ['Alice', 'Bob', 'Charlie'] });
      break;

    case 'POST':
      // Handle POST request - create new user
      const newUser = req.body.name;
      res.status(201).json({ message: `User ${newUser} created!` });
      break;

    default:
      // Handle other HTTP methods
      res.setHeader('Allow', ['GET', 'POST']);
      res.status(405).end(`Method ${method} Not Allowed`);
  }
}

//Next js Tutorial
  • GET: Fetches a list of users.
  • POST: Creates a new user based on the request body.
Testing:
  • A GET request to /api/users returns a list of users.
  • A POST request with {"name": "John"} to /api/users returns a message User John created!.

6.4 Parsing Request Body

For POST or PUT requests, you might need to access the request body, which contains the data sent from the client. By default, Next.js automatically parses incoming JSON data. You can access it using req.body.

Example: Accessing Request Body
// pages/api/login.js

export default function handler(req, res) {
  const { method } = req;

  if (method === 'POST') {
    const { username, password } = req.body;
    if (username === 'admin' && password === 'secret') {
      res.status(200).json({ message: 'Login successful' });
    } else {
      res.status(401).json({ error: 'Invalid credentials' });
    }
  } else {
    res.setHeader('Allow', ['POST']);
    res.status(405).end(`Method ${method} Not Allowed`);
  }
}

//Next js Tutorial

In this example:

  • The route expects a POST request with a JSON body containing username and password.
  • It checks if the credentials are valid and returns a success or failure message.

6.5 Dynamic API Routes

Next.js also supports dynamic API routes, allowing you to create routes that capture parts of the URL as parameters (similar to dynamic pages).

Example: Dynamic API Route
// pages/api/user/[id].js

export default function handler(req, res) {
  const { id } = req.query; // Access the dynamic route parameter

  if (req.method === 'GET') {
    res.status(200).json({ message: `Fetching user with ID ${id}` });
  } else {
    res.setHeader('Allow', ['GET']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

//Next js Tutorial

When you visit /api/user/123, the server responds with:

{
  "message": "Fetching user with ID 123"
}
  • The dynamic part [id] is captured using req.query.id.

6.6 Using Environment Variables

Sometimes, your API routes might need to connect to external services or use secret keys. You can store sensitive information like API keys or database credentials in environment variables by using a .env file.

Example: Using Environment Variables
  1. Create a .env.local file in your project:
API_KEY=my-secret-api-key

//Next js Tutorial
  1. Access the environment variable inside your API route:
// pages/api/external.js

export default async function handler(req, res) {
  const apiKey = process.env.API_KEY;

  const response = await fetch(`https://api.example.com/data?key=${apiKey}`);
  const data = await response.json();

  res.status(200).json(data);
}

//Next js Tutorial
  • Note: Environment variables in Next.js must be prefixed with NEXT_PUBLIC_ to be exposed to the client-side. Otherwise, they are only available on the server side.

6.7 Middleware in API Routes

Next.js allows you to create custom middleware functions to process incoming requests before they reach the API route handler. This is useful for tasks like authentication, validation, or logging.

Example: Basic Middleware
// middleware/auth.js

export default function checkAuth(req, res, next) {
  const { authorization } = req.headers;

  if (authorization === 'Bearer token') {
    next(); // Continue to the API route
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
}

//Next js Tutorial

You can apply this middleware to your API route:

// pages/api/secure-data.js
import checkAuth from '../../middleware/auth';

export default function handler(req, res) {
  checkAuth(req, res, () => {
    res.status(200).json({ message: 'This is secure data' });
  });
}

//Next js Tutorial

In this example:

  • The checkAuth middleware verifies if the request has a valid authorization header.
  • If valid, it calls the actual API route handler; otherwise, it returns a 401 Unauthorized response.

6.8 Using API Routes for Full-Stack Development

API Routes can serve as a bridge between the frontend and backend, allowing you to:

  • Fetch data from databases: Connect to MongoDB, PostgreSQL, or any other database.
  • Handle form submissions: Process user inputs from forms or frontend pages.
  • Create custom APIs: Build APIs for third-party integrations (e.g., Stripe, payment gateways).
  • Manage authentication: Implement user login systems with JWT or OAuth.

6.9 Conclusion

API Routes in Next.js provide a powerful way to integrate backend functionality within your app without setting up a separate server. Whether you’re handling form submissions, building CRUD APIs, or connecting to third-party services, API routes allow you to create robust full-stack applications with ease.

7. Image Optimization in Next.js

One of the key features of Next.js is its built-in image optimization. This feature helps developers load images efficiently, which improves the performance and user experience of their web applications. The next/image component provides automatic image resizing, lazy loading, and support for modern image formats like WebP, allowing you to deliver optimized images without any extra configuration.

7.1 Why Image Optimization Matters

Images are often the largest assets on a website, contributing significantly to load times and performance. Optimizing images ensures that:

  • Images are resized based on the device’s viewport.
  • Only the necessary images are loaded (lazy loading).
  • Images are delivered in the most efficient format (e.g., WebP when supported).

With Next.js, this is handled automatically with the next/image component.

7.2 The next/image Component

Next.js provides the next/image component for handling images. This component optimizes images on-demand, serving resized and compressed versions of the original image based on the device and network conditions.

Basic Usage:
import Image from 'next/image';

export default function Home() {
  return (
    <div>
      <Image 
        src="/images/example.jpg" 
        alt="Example Image" 
        width={800} 
        height={600} 
      />
    </div>
  );
}

//Next js Tutorial
  • src: The path to the image. This can be a local image or an external URL.
  • alt: Describes the image for accessibility and SEO purposes.
  • width and height: Define the intrinsic dimensions of the image.

The next/image component automatically adjusts the image based on the user’s device, resulting in optimal loading.

7.3 Lazy Loading

By default, Next.js lazily loads images, meaning that images are only loaded when they appear in the viewport. This improves performance as the browser doesn’t need to load all images upfront.

Example of Lazy Loading:
<Image 
  src="/images/lazy-image.jpg" 
  alt="Lazy Loaded Image" 
  width={1200} 
  height={800} 
  placeholder="blur"
/>

//Next js Tutorial
  • placeholder="blur": Displays a blurred version of the image while it is loading, providing a better user experience.

7.4 Image Sizes and Responsive Images

You can make your images responsive by providing the sizes prop, which defines how the image should behave based on the screen size. This allows you to serve different image sizes for different devices (e.g., mobile, tablet, desktop).

Example:
<Image
  src="/images/responsive.jpg"
  alt="Responsive Image"
  width={1200}
  height={800}
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

//Next js Tutorial
  • sizes: Determines the width the image should take up at different screen sizes.
  • For screens smaller than 768px, the image will take up 100% of the viewport width.
  • For screens smaller than 1200px, the image will take up 50%.
  • On larger screens, it will take up 33%.

7.5 Image Formats (WebP)

Next.js can automatically serve images in modern formats like WebP (if supported by the browser), which results in faster load times. You don’t need to manually create WebP versions of your images—Next.js handles this for you.

7.6 External Images

Next.js allows you to use images hosted on external domains. However, you need to configure the next.config.js file to specify which domains are allowed to load images.

Example:
// next.config.js

module.exports = {
  images: {
    domains: ['example.com', 'cdn.example.com'],
  },
};

//Next js Tutorial

Once configured, you can use the next/image component to display images from these external sources:

<Image 
  src="https://cdn.example.com/external-image.jpg"
  alt="External Image"
  width={800}
  height={600}
/>

//Next js Tutorial

7.7 Image Optimization for Performance

Next.js optimizes images for performance by:

  • Resizing images dynamically for different screen sizes and DPR (device pixel ratio).
  • Serving modern image formats like WebP when supported by the client.
  • Compressing images to reduce file sizes while maintaining quality.
  • Lazy loading images for faster initial page loads.

7.8 Unoptimized Images

In some cases, you may not want Next.js to optimize an image, such as when using an external service that already optimizes images. You can opt out of Next.js optimization using the unoptimized prop.

Example:
<Image 
  src="https://example.com/pre-optimized-image.jpg" 
  alt="Unoptimized Image" 
  width={1200} 
  height={800} 
  unoptimized 
/>

//Next js Tutorial

This tells Next.js to serve the image without applying any additional optimization.

7.9 Custom Loaders

Next.js allows you to implement a custom loader to define how your images are loaded. This is useful if you are using an external service like Cloudinary or Imgix, which provides image optimization features.

Example: Using a Custom Cloudinary Loader
// Custom Cloudinary Loader
const cloudinaryLoader = ({ src, width, quality }) => {
  return `https://res.cloudinary.com/demo/image/fetch/q_${quality || 75}/w_${width}/${src}`;
};

// Usage in next/image
<Image
  loader={cloudinaryLoader}
  src="https://example.com/image.jpg"
  alt="Image with Cloudinary Loader"
  width={1200}
  height={800}
/>

//Next js Tutorial
  • loader: A function that takes an object containing src, width, and quality parameters and returns the URL to the optimized image.

7.10 Placeholder Options

Besides the blur placeholder, Next.js allows for different types of placeholders while the image is loading. The two common options are:

  1. Blur Placeholder: Creates a blurry version of the image while it’s loading.
  2. Empty Placeholder: No placeholder is displayed (default behavior).
<Image 
  src="/images/example.jpg"
  alt="Image with Blur Placeholder"
  width={1200}
  height={800}
  placeholder="blur" // Optional
  blurDataURL="data:image/jpeg;base64,..." // Optional custom blur
/>

//Next js Tutorial

7.11 Conclusion

Next.js simplifies image optimization with its built-in next/image component. By utilizing this feature, you can:

  • Automatically optimize images for better performance.
  • Ensure that images are responsive and lazy-loaded.
  • Serve images in modern formats like WebP for supported browsers.
  • Easily integrate images from external domains or services like Cloudinary.

With image optimization being handled automatically by Next.js, you can focus on building your application while ensuring great performance across devices.

8. Internationalization (i18n) in Next.js

Internationalization (i18n) is the process of making your web application adaptable to different languages and regions. Next.js has built-in support for i18n, allowing you to easily create multi-language websites without needing third-party libraries. This feature helps localize routes, static content, and dynamic translations across different languages.

8.1 Why Internationalization?

If your application serves a global audience, you need to ensure that:

  • Content is available in multiple languages.
  • Users are redirected to their language or region automatically.
  • URL paths reflect the language (e.g., /en, /fr).
  • You can handle dynamic translations of both content and URLs.

Next.js makes it simple to achieve this using the i18n configuration.

8.2 i18n Configuration in Next.js

To set up internationalization, you need to configure the i18n property in next.config.js. This property tells Next.js which locales your application supports and the default language to serve.

Basic Setup:
// next.config.js

module.exports = {
  i18n: {
    locales: ['en', 'fr', 'de'], // List of supported languages
    defaultLocale: 'en',         // Default language
    localeDetection: true,       // Automatically detect user locale
  },
};

//Next js Tutorial
  • locales: Array of supported languages (e.g., ‘en’ for English, ‘fr’ for French, ‘de’ for German).
  • defaultLocale: Default language when the user’s preferred language is not available.
  • localeDetection: Determines if Next.js should automatically detect the user’s language based on browser settings.

Once this configuration is in place, Next.js will generate localized routes such as /en, /fr, and /de.

8.3 Localized Routing

Next.js automatically handles localized routing based on the configured locales. For example, if your app supports English and French, and you have a page at /about, Next.js will create the following routes:

  • /en/about
  • /fr/about

Users visiting the site will be served the appropriate language based on the locale detected from their browser or chosen explicitly.

Example: Setting up a localized route
// pages/[locale]/about.js

export default function About() {
  return (
    <div>
      <h1>About Page</h1>
      <p>This page is available in multiple languages.</p>
    </div>
  );
}

//Next js Tutorial

Next.js automatically serves this page under the /about route for all supported locales, and the language will vary based on the user’s selected locale.

8.4 Dynamic Localization with next/router

Next.js provides the next/router API, allowing you to programmatically control routing and dynamically change languages in your application.

Example: Switching Languages
import { useRouter } from 'next/router';

export default function LanguageSwitcher() {
  const router = useRouter();
  const { locale } = router;

  const switchLanguage = (newLocale) => {
    router.push(router.pathname, router.asPath, { locale: newLocale });
  };

  return (
    <div>
      <button onClick={() => switchLanguage('en')}>English</button>
      <button onClick={() => switchLanguage('fr')}>Français</button>
    </div>
  );
}

//Next js Tutorial

In this example:

  • The current locale is accessed through router.locale.
  • A user can switch languages by clicking a button, which updates the locale and reloads the current page in the new language.

8.5 Translations with next-translate or react-i18next

Next.js does not handle the translation of strings directly. You can use a library like next-translate or react-i18next to manage string translations in your application. These libraries allow you to store your translations in JSON files and load them dynamically based on the user’s locale.

Example: Using next-translate
  1. Install the package:
npm install next-translate

//Next js Tutorial
  1. Create translation files (e.g., locales/en/common.json, locales/fr/common.json):
// locales/en/common.json
{
  "welcome": "Welcome to Next.js!"
}

// locales/fr/common.json
{
  "welcome": "Bienvenue à Next.js!"
}

//Next js Tutorial
  1. Load translations in a page or component:
import useTranslation from 'next-translate/useTranslation';

export default function Home() {
  const { t } = useTranslation('common');

  return (
    <div>
      <h1>{t('welcome')}</h1>
    </div>
  );
}

//Next js Tutorial
  • useTranslation: A hook that loads the appropriate translation file and provides a t function to retrieve translated strings.

8.6 Handling Static and Dynamic Content

Next.js supports both static and dynamic content translation. For static content, translations can be loaded during build time, while dynamic content can be translated on the fly using libraries or APIs.

Example: Using getStaticProps for Static Localization

You can use Next.js’ getStaticProps to fetch translations during build time for static pages.

import useTranslation from 'next-translate/useTranslation';

export async function getStaticProps({ locale }) {
  return {
    props: {
      messages: await import(`../locales/${locale}/common.json`),
    },
  };
}

export default function Home({ messages }) {
  const { t } = useTranslation();

  return <h1>{t('welcome')}</h1>;
}

//Next js Tutorial
  • getStaticProps fetches the appropriate translation file for the current locale at build time.

8.7 Redirection Based on Locale

You can automatically redirect users to their preferred language using the redirect property in Next.js. This can be configured in getServerSideProps or getStaticProps.

Example: Locale Redirection
export async function getServerSideProps({ req }) {
  const locale = req.headers['accept-language'].split(',')[0]; // Detect user locale
  return {
    redirect: {
      destination: `/${locale}/home`, // Redirect to localized home page
      permanent: false,
    },
  };
}

//Next js Tutorial

In this example:

  • The user’s locale is detected based on the accept-language header.
  • The user is redirected to the corresponding language version of the page.

8.8 SEO and Localization

When dealing with multiple languages, it’s important to ensure that your website is SEO-friendly for all locales. Next.js automatically adds the correct rel="alternate" links in the <head> for search engines to know which language versions are available.

Example: Adding hreflang Tags

Next.js handles hreflang tags automatically for different locales, but you can customize them by using the <Head> component to improve SEO.

import Head from 'next/head';

export default function Home() {
  return (
    <Head>
      <link rel="alternate" href="/en" hrefLang="en" />
      <link rel="alternate" href="/fr" hrefLang="fr" />
    </Head>
  );
}

//Next js Tutorial

8.9 Conclusion

Internationalization (i18n) in Next.js allows you to build applications that serve users in multiple languages with ease. With features like localized routing, automatic locale detection, and dynamic translations, Next.js makes it simple to create globalized applications. By combining these features with translation libraries like next-translate, you can ensure your application is accessible to users across different languages and regions.

9. Performance Optimization in Next.js

Optimizing the performance of your Next.js application ensures faster load times, better SEO, and an overall smoother user experience. Next.js comes with several built-in features that enable efficient performance, such as static site generation, code splitting, and image optimization. However, you can take further steps to improve performance.

9.1 Why Performance Optimization Matters

  • User Experience: Faster load times improve user engagement and reduce bounce rates.
  • SEO: Google and other search engines prioritize fast-loading websites, affecting your ranking.
  • Mobile Performance: Optimizing for mobile ensures a consistent experience for users on slower networks or devices.

Next.js provides tools to automatically optimize your site, but understanding how these tools work will help you further fine-tune performance.

9.2 Pre-rendering: Static Generation (SSG) vs Server-side Rendering (SSR)

Next.js supports two types of pre-rendering: Static Generation (SSG) and Server-side Rendering (SSR). Choosing the right strategy for each page can significantly impact performance.

  • Static Generation (SSG): Pages are generated at build time and served as static HTML, resulting in faster performance.
  • Server-side Rendering (SSR): Pages are rendered on each request, which can add server latency but ensures fresh data.
Best Practice: Prefer Static Generation whenever possible.
  • Static Pages (SSG): Pages that don’t frequently change (like blog posts or product pages) should be statically generated.
  • Dynamic Pages (SSR): Pages that need to show frequently updated data (like user profiles or dashboards) should use server-side rendering.
Example: Static Generation with getStaticProps
export async function getStaticProps() {
  const data = await fetch('https://api.example.com/data');
  return {
    props: { data },
  };
}

//Next js Tutorial
Example: Server-side Rendering with getServerSideProps
export async function getServerSideProps() {
  const data = await fetch('https://api.example.com/data');
  return {
    props: { data },
  };
}

//Next js Tutorial

9.3 Code Splitting and Dynamic Imports

Next.js automatically splits your code into smaller bundles. Only the necessary JavaScript for the current page is loaded, improving performance. However, you can further optimize performance using dynamic imports to load components only when needed.

Example: Dynamic Import of Components
import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(() => import('../components/HeavyComponent'));

export default function Page() {
  return (
    <div>
      <h1>Welcome to the Page</h1>
      <DynamicComponent />
    </div>
  );
}

//Next js Tutorial
  • Benefit: Large components are loaded only when they are actually needed, reducing the initial page load size.

9.4 Image Optimization

As discussed in section 7, Next.js has built-in image optimization using the next/image component. By optimizing image size, format, and loading behavior, you can greatly improve performance.

  • Use responsive images with different sizes for different screen sizes.
  • Enable lazy loading to defer loading off-screen images.
  • Serve images in modern formats like WebP for faster loading.

9.5 Caching Strategies

Next.js provides several ways to implement caching, improving both performance and user experience.

  • Static Caching: With Static Generation, Next.js serves pre-built pages directly from the CDN, making load times incredibly fast.
  • Incremental Static Regeneration (ISR): With ISR, you can statically generate pages but also regenerate them in the background as data changes.
Example: Incremental Static Regeneration
export async function getStaticProps() {
  const data = await fetch('https://api.example.com/data');
  return {
    props: { data },
    revalidate: 60,  // Revalidate page every 60 seconds
  };
}

//Next js Tutorial

This ensures pages are served statically but are updated in the background when needed, keeping your data fresh without slowing down page loads.

9.6 Minification and Compression

Next.js automatically applies JavaScript minification using Terser, which removes unnecessary code to reduce the size of your JavaScript files. In addition, compression techniques like Brotli or Gzip can further reduce the size of your assets.

Enable Compression:

Ensure that your hosting platform is configured to use Brotli or Gzip compression to deliver smaller files to the client.

9.7 Lazy Loading for Non-critical Resources

Next.js allows for lazy loading of non-critical resources (like images, videos, and components) to optimize the initial page load. Lazy loading ensures that only the elements in the viewport are loaded initially, and others are deferred until they come into view.

Example: Lazy Loading for Images
<Image 
  src="/path/to/image.jpg" 
  alt="Lazy Loaded Image"
  width={800} 
  height={600} 
  loading="lazy" 
/>

//Next js Tutorial

9.8 Optimizing Fonts

Fonts can have a significant impact on performance. Next.js allows you to optimize font loading by:

  • Using the next/font package for self-hosting fonts.
  • Preloading fonts to avoid render-blocking.
  • Minimizing the number of font weights and styles you use.
Example: Preloading Fonts
<link rel="preload" href="/fonts/your-font.woff2" as="font" type="font/woff2" crossorigin="anonymous" />

//Next js Tutorial

Next.js automatically prefetches linked pages when they enter the viewport, speeding up navigation between pages. By using the next/link component, Next.js fetches the JavaScript for the linked page in advance, making navigation almost instantaneous.

import Link from 'next/link';

export default function Home() {
  return (
    <div>
      <Link href="/about">
        <a>About Page</a>
      </Link>
    </div>
  );
}

//Next js Tutorial
  • Benefit: Pages are preloaded when the user hovers over or scrolls to a link, reducing the time to navigate between pages.

9.10 Avoid Unnecessary Re-renders

Using React hooks like useMemo and useCallback can help avoid unnecessary re-renders, improving performance. This is especially important in large components or components that receive many props.

Example: Using useMemo
import { useMemo } from 'react';

const expensiveCalculation = (num) => {
  return num * 2;  // Simulate an expensive calculation
};

export default function Component({ number }) {
  const result = useMemo(() => expensiveCalculation(number), [number]);

  return <div>{result}</div>;
}

//Next js Tutorial

9.11 Lighthouse Audits

Google Lighthouse is a great tool to audit your site’s performance, accessibility, and SEO. Running a Lighthouse audit can help identify bottlenecks, such as unoptimized images, large JavaScript files, and slow server response times.

Steps to Run a Lighthouse Audit:
  1. Open your site in Google Chrome.
  2. Open the DevTools (right-click > Inspect).
  3. Go to the Lighthouse tab.
  4. Run the audit and review the suggestions for performance improvements.

9.12 Monitoring and Analytics

Monitoring the performance of your Next.js application in production is key to identifying slowdowns and bottlenecks.

  • Use Google Analytics, Vercel Analytics, or Web Vitals to monitor user experience metrics like First Contentful Paint (FCP) and Time to Interactive (TTI).
  • Tools like Sentry or LogRocket can help monitor performance issues in real-time.

9.13 Progressive Web Apps (PWAs)

Next.js allows you to turn your application into a Progressive Web App (PWA) by adding service workers and offline support, improving performance and reliability on slow networks.

Example: Setting Up a PWA
  1. Install the next-pwa package:
npm install next-pwa

//Next js Tutorial
  1. Add a service worker to your next.config.js:
const withPWA = require('next-pwa')({
  dest: 'public',
});

module.exports = withPWA({
  // Your other configurations
});

//Next js Tutorial

This setup allows your application to be cached and run offline, improving performance for users on slow networks.

9.14 Conclusion

Next.js comes with many built-in features to optimize performance, from static generation and dynamic imports to image optimization and code splitting. By combining these features with additional techniques like caching, lazy loading, and using tools like Lighthouse for audits, you can significantly improve your application’s performance across devices and networks. Optimizing performance not only leads to better user experiences but also improves SEO and overall site success.

10. Authentication in Next.js

Authentication is a crucial part of any web application. Next.js provides various ways to implement authentication, depending on your specific requirements (e.g., server-side authentication, client-side authentication, third-party providers). This section will cover the common approaches for adding authentication to a Next.js app, including using OAuth, JWT, session-based authentication, and integrating third-party libraries like NextAuth.js.

10.1 Types of Authentication

  1. Client-Side Authentication: Authentication logic is handled in the browser, typically with JWTs (JSON Web Tokens) stored in localStorage or cookies.
  2. Server-Side Authentication: Authentication logic is handled on the server, allowing for more secure handling of user data.
  3. Third-Party Authentication Providers: Services like Google, Facebook, GitHub, etc., handle authentication via OAuth.

Each of these approaches can be integrated into Next.js, depending on the complexity and security needs of your application.

10.2 Setting Up Authentication with NextAuth.js

NextAuth.js is a popular authentication solution for Next.js, offering easy integration with multiple authentication providers and support for both client-side and server-side authentication. It simplifies the process of handling OAuth providers (like Google, GitHub) and allows session-based authentication.

Steps to Integrate NextAuth.js
  1. Install NextAuth.js
npm install next-auth

//Next js Tutorial
  1. Create an API Route for Authentication

In Next.js, API routes allow you to implement authentication on the server. You can create a new route for NextAuth.js in the /pages/api/auth/[...nextauth].js file.

// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  providers: [
    Providers.GitHub({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    }),
    Providers.Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  pages: {
    signIn: "/auth/signin", // Custom sign-in page
  },
});

//Next js Tutorial
  • OAuth Providers: NextAuth.js supports many providers out of the box, including Google, GitHub, Facebook, Twitter, etc. You just need to configure them with your client ID and secret.
  • Custom Pages: You can create custom sign-in or error pages by overriding the default NextAuth.js pages.
  1. Add a Sign-in Page

Next, you need to create a page for user sign-in:

// pages/auth/signin.js
import { signIn, signOut, useSession } from "next-auth/react";

export default function SignIn() {
  const { data: session } = useSession();

  if (session) {
    return (
      <>
        <h1>Welcome, {session.user.name}</h1>
        <button onClick={() => signOut()}>Sign Out</button>
      </>
    );
  }

  return (
    <>
      <h1>Please Sign In</h1>
      <button onClick={() => signIn("github")}>Sign In with GitHub</button>
      <button onClick={() => signIn("google")}>Sign In with Google</button>
    </>
  );
}
  • signIn and signOut: These methods are provided by NextAuth.js for signing in with a provider and signing out of the session.
  • useSession: A React hook to access the current session (user’s authentication state).
  1. Securing Pages (Client-side)

You can protect specific pages by checking if a user is authenticated before rendering the content:

// pages/dashboard.js
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function Dashboard() {
  const { data: session, status } = useSession();
  const router = useRouter();

  useEffect(() => {
    if (status === "unauthenticated") {
      router.push("/auth/signin");
    }
  }, [status]);

  if (status === "loading") {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h1>Welcome to your dashboard, {session.user.name}!</h1>
    </div>
  );
}
  • Redirect Unauthenticated Users: If the user is not authenticated, they are redirected to the sign-in page.

10.3 Session-Based Authentication

NextAuth.js uses session-based authentication by default, which securely stores user data on the server and sends only a session token to the client. This approach is more secure than using JWTs stored in localStorage.

Managing Sessions:
  • useSession(): A hook that provides access to the current session (if authenticated).
  • getSession(): A function to retrieve the current session on the server side (useful for protecting API routes).
Example: Protecting API Routes

You can secure API routes by checking if a valid session exists before processing the request:

// pages/api/secure-data.js
import { getSession } from "next-auth/react";

export default async (req, res) => {
  const session = await getSession({ req });

  if (!session) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  // Continue processing the request if authenticated
  res.status(200).json({ message: "Secure data accessed", user: session.user });
};

10.4 JWT (JSON Web Token) Authentication

While NextAuth.js is session-based by default, you can configure it to use JWTs for authentication if you prefer a stateless, token-based approach.

Example: Enabling JWTs in NextAuth.js
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  providers: [
    Providers.Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  session: {
    jwt: true, // Enable JWT instead of session
  },
  jwt: {
    secret: process.env.JWT_SECRET,
  },
});
  • JWTs: These are encoded tokens that contain user information. They can be stored in localStorage or cookies and passed in headers when making requests to the server.
  • Use Cases: JWTs are useful in scenarios where you need to authenticate users in a stateless manner, such as when building a headless API or mobile app.

10.5 Client-Side Authentication with JWTs

If you’re not using NextAuth.js and want to handle authentication manually using JWTs, you can store the token in localStorage or cookies after user login.

Example: Handling JWT Authentication Manually
  1. Login and Store JWT:
// pages/api/auth/login.js

export default async (req, res) => {
  const { email, password } = req.body;

  const response = await fetch('https://your-backend.com/api/login', {
    method: 'POST',
    body: JSON.stringify({ email, password }),
    headers: { 'Content-Type': 'application/json' },
  });

  const { token } = await response.json();

  // Store JWT in cookies for security
  res.setHeader(
    "Set-Cookie",
    `token=${token}; HttpOnly; Path=/; Max-Age=3600;`
  );

  res.status(200).json({ message: "Login successful" });
};
  1. Check JWT on Client Side:
import { useEffect, useState } from "react";

export default function Dashboard() {
  const [token, setToken] = useState(null);

  useEffect(() => {
    const token = document.cookie.split("=")[1];
    setToken(token);
  }, []);

  if (!token) {
    return <p>Please log in to access the dashboard</p>;
  }

  return <h1>Welcome to the Dashboard</h1>;
}
  • Security Tip: Avoid storing JWTs in localStorage, as they are vulnerable to cross-site scripting (XSS). Using HttpOnly cookies is a safer option.

10.6 OAuth with Third-Party Providers

You can integrate third-party OAuth providers (like Google, GitHub, Facebook) with ease in Next.js. This is supported directly by NextAuth.js, but you can also implement OAuth manually.

Example: Manual OAuth Flow
  1. Redirect the user to the provider’s OAuth page.
  2. Handle the OAuth callback on your server.
  3. Exchange the authorization code for an access token.
  4. Store the access token in a session or JWT.

10.7 Middleware for Authentication

You can use middleware in Next.js to protect routes or run authentication checks before rendering a page. Middleware can be run on any request, whether it’s client-side or server-side.

Example: Using Middleware for Authentication
// middleware.js
import { getSession } from "next-auth/react";

export default async function middleware(req) {
  const session = await getSession({ req });

  if (!session) {
    return new Response(null, {
      status: 302,
      headers: {
        Location: "/auth/signin",
      },
    });
  }
}
  • This middleware checks for an authenticated session. If no session is found, it redirects the

11. Deploying Next.js

Deploying a Next.js application involves hosting it on a platform that supports either static site generation (SSG), server-side rendering (SSR), or hybrid modes. Next.js makes deployment simple by supporting various platforms like Vercel, Netlify, and AWS, among others. In this section, you’ll learn the key steps to deploy a Next.js app and optimize it for production.

11.1 Vercel Deployment

Vercel is the company behind Next.js and offers seamless integration for deploying Next.js apps. It supports both static and server-rendered Next.js apps out of the box.

Steps to Deploy on Vercel:
  1. Install Vercel CLI (Optional)

You can install the Vercel CLI globally to deploy projects directly from your terminal:

npm install -g vercel
  1. Initialize a New Vercel Project

If you have a Next.js project, you can link it to Vercel:

vercel
  1. Configure Project Settings (Optional)

Follow the CLI prompts to configure your project. You can specify whether it’s a static site, API routes, or server-rendered.

  1. Deploy the Project

Once linked, you can deploy your Next.js app with:

vercel --prod

This will deploy your app in production mode, optimizing it for performance.

  1. Automatic Deployments via Git Integration

Vercel allows automatic deployments by linking your project to a GitHub, GitLab, or Bitbucket repository. Every time you push changes to your repository, Vercel will trigger a new deployment.

  • Preview Deployments: For each new Git branch or pull request, Vercel creates a unique deployment URL for preview purposes.
  • Production Deployments: Changes pushed to the main branch automatically deploy to the production environment.

11.2 Netlify Deployment

Netlify is another popular platform for deploying static and dynamic Next.js apps. It offers powerful features like continuous integration, serverless functions, and custom domain management.

Steps to Deploy on Netlify:
  1. Create a Netlify Account: Sign up at Netlify.
  2. Link Your Repository: In the Netlify dashboard, connect your GitHub, GitLab, or Bitbucket repository. Netlify will automatically detect your Next.js app.
  3. Configure Build Settings:
  • Build Command: npm run build
  • Publish Directory: .next
  1. Deploy: Click “Deploy Site” to trigger the first deployment.
  2. Custom Domain and HTTPS: Netlify allows easy setup of custom domains with automatic HTTPS via Let’s Encrypt.

11.3 Deployment on AWS (Amazon Web Services)

AWS provides several services like AWS Amplify, EC2, S3, and Lambda for deploying Next.js apps, making it suitable for both static and dynamic use cases.

Deploying with AWS Amplify:

AWS Amplify is a popular choice for static apps with SSG and client-side rendering.

  1. Create an AWS Account: Sign up at AWS.
  2. Install Amplify CLI (if using CLI for deployment):
   npm install -g @aws-amplify/cli
  1. Initialize Amplify:
   amplify init
  1. Connect Your Repository: In the AWS Amplify console, connect your GitHub, GitLab, or Bitbucket repository.
  2. Set Build Settings:
  • Build Command: npm run build
  • Publish Directory: .next
  1. Deploy: Once the build completes, your Next.js app will be live on AWS.
Deploying with AWS S3 and CloudFront (Static Export):

For static-only Next.js apps, you can export the app as static HTML and host it on S3 with CloudFront.

  1. Export the App:
   next build && next export
  1. Upload to S3: Upload the generated static files from the out directory to an S3 bucket.
  2. Set Up CloudFront: Use AWS CloudFront to serve the static files via a content delivery network (CDN) for better performance.

11.4 Deploying on DigitalOcean

DigitalOcean provides hosting options through App Platform or self-managed Droplets.

Steps to Deploy on DigitalOcean App Platform:
  1. Create a DigitalOcean Account: Sign up at DigitalOcean.
  2. Create a New App: In the App Platform dashboard, click “Create App” and connect your GitHub repository.
  3. Configure Build Settings:
  • Build Command: npm run build
  • Start Command: npm start
  1. Select Environment Variables: Add any required environment variables (e.g., API keys).
  2. Deploy: DigitalOcean will handle the deployment process and provide a live URL.

11.5 Deploying on Heroku

Heroku is a flexible cloud platform that supports dynamic Next.js apps with server-side rendering.

Steps to Deploy on Heroku:
  1. Install the Heroku CLI:
   npm install -g heroku
  1. Log In to Heroku:
   heroku login
  1. Create a New Heroku App:
   heroku create
  1. Add Buildpack for Next.js:
   heroku buildpacks:add mars/create-react-app
  1. Deploy to Heroku:
   git push heroku main

11.6 Optimizing for Production

Next.js has built-in features to optimize your app for production deployment:

  1. Production Mode: Always run your app in production mode using:
   npm run build && npm run start
  1. Minification: Next.js automatically minifies JavaScript and CSS for better performance.
  2. Static Optimization: Use static generation (getStaticProps) whenever possible to serve pre-rendered HTML, reducing server load.
  3. Image Optimization: Leverage Next.js’s built-in image optimization features for lazy loading, automatic resizing, and format conversion.
  4. Environment Variables: Use environment variables for configuration in production. Ensure you do not expose sensitive information in your client-side code.
  5. Custom Domain: Configure your domain on your hosting platform for branding and better SEO.

11.7 Monitoring and Analytics

After deployment, monitoring your Next.js app is essential to ensure optimal performance:

  • Vercel Analytics: Vercel provides built-in performance analytics for Next.js apps.
  • Google Lighthouse: Use Lighthouse to audit your app’s performance, accessibility, and SEO.
  • Sentry or LogRocket: Implement error tracking and logging to monitor real-time user issues.
  • Google Analytics: Integrate Google Analytics for traffic monitoring and user behavior analysis.

By following these steps, you can successfully deploy a Next.js application on various platforms while ensuring performance and scalability.

12. Bonus Topics

In addition to the core concepts covered in the previous sections, several advanced features and best practices can enhance your Next.js application. These bonus topics will give you a broader understanding of Next.js and help you build more robust applications.

12.1 Custom Server Configuration

While Next.js has a built-in server, you can create a custom server using Node.js or Express to add additional functionality, such as custom routing or middleware.

Example: Creating a Custom Server with Express
  1. Install Express:
   npm install express
  1. Create a Custom Server File:
   // server.js
   const express = require("express");
   const next = require("next");

   const dev = process.env.NODE_ENV !== "production";
   const app = next({ dev });
   const handle = app.getRequestHandler();

   app.prepare().then(() => {
     const server = express();

     // Custom route example
     server.get("/custom-route", (req, res) => {
       return app.render(req, res, "/custom-page");
     });

     server.all("*", (req, res) => {
       return handle(req, res);
     });

     server.listen(3000, (err) => {
       if (err) throw err;
       console.log("Server is running on http://localhost:3000");
     });
   });
  1. Run the Custom Server: Update your package.json scripts:
   "scripts": {
     "dev": "node server.js",
     "build": "next build",
     "start": "NODE_ENV=production node server.js"
   }

12.2 Middleware in Next.js

Middleware in Next.js allows you to run custom code before requests are completed. It can be used for tasks like authentication, logging, and modifying request/response headers.

Example: Adding Middleware

Create a middleware file:

// middleware.js
import { NextResponse } from 'next/server';

export function middleware(req) {
  // Perform some checks or logging
  console.log(`Request made to: ${req.nextUrl.pathname}`);

  // Redirect users who are not authenticated
  if (!req.cookies.get("token")) {
    return NextResponse.redirect(new URL("/auth/signin", req.url));
  }

  return NextResponse.next();
}
  • Usage: Middleware can be applied globally or to specific routes.

12.3 TypeScript Support

Next.js has built-in support for TypeScript, making it easier to catch errors during development and providing better tooling.

Steps to Add TypeScript
  1. Install TypeScript and Types:
   npm install --save-dev typescript @types/react @types/node
  1. Create a tsconfig.json file: You can run npx tsc --init to generate a default tsconfig.json, or you can let Next.js create it when you start the development server.
  2. Rename Files: Change your file extensions from .js to .tsx for React components or .ts for TypeScript files.

12.4 Testing Next.js Applications

Testing is vital for maintaining the quality of your applications. You can use libraries like Jest and React Testing Library for unit and integration tests.

Example: Setting Up Jest
  1. Install Jest and React Testing Library:
   npm install --save-dev jest @testing-library/react @testing-library/jest-dom
  1. Create a Test File:
   // __tests__/index.test.js
   import { render, screen } from '@testing-library/react';
   import Home from '../pages/index';

   test('renders the home page', () => {
     render(<Home />);
     expect(screen.getByText(/Welcome to Next.js/i)).toBeInTheDocument();
   });
  1. Run Tests: Add a test script to your package.json:
   "scripts": {
     "test": "jest"
   }

12.5 Using GraphQL with Next.js

Next.js can easily integrate with GraphQL APIs using libraries like Apollo Client.

Example: Setting Up Apollo Client
  1. Install Apollo Client:
   npm install @apollo/client graphql
  1. Create Apollo Client:
   // lib/apolloClient.js
   import { ApolloClient, InMemoryCache } from '@apollo/client';

   const client = new ApolloClient({
     uri: 'https://your-graphql-endpoint.com/graphql',
     cache: new InMemoryCache(),
   });

   export default client;
  1. Use Apollo Client in Your Pages:
   // pages/index.js
   import { ApolloProvider } from '@apollo/client';
   import client from '../lib/apolloClient';

   function Home() {
     return (
       <ApolloProvider client={client}>
         {/* Your components go here */}
       </ApolloProvider>
     );
   }

   export default Home;

12.6 Static Site Generation (SSG) vs. Server-Side Rendering (SSR)

Understanding when to use SSG and SSR is crucial for optimizing performance and improving user experience.

  • Static Site Generation (SSG): Generates HTML at build time. Best for content that doesn’t change frequently, like blogs or documentation.
  • Server-Side Rendering (SSR): Generates HTML on each request. Suitable for dynamic content that changes frequently or requires user-specific data.

12.7 Incremental Static Regeneration (ISR)

ISR allows you to update static content without rebuilding the entire site. You can set a revalidation time for specific pages.

Example: Using ISR
// pages/blog/[id].js
export async function getStaticProps({ params }) {
  const post = await fetchPost(params.id);

  return {
    props: { post },
    revalidate: 60, // Revalidate every 60 seconds
  };
}

12.8 Advanced Image Optimization

Next.js provides advanced image optimization features through the next/image component. You can use features like lazy loading, automatic format selection, and resizing.

Example: Using next/image
import Image from 'next/image';

function MyComponent() {
  return (
    <Image
      src="/images/photo.jpg"
      alt="Description of image"
      width={500}
      height={300}
      layout="responsive" // Responsive layout
      priority // Loads the image with high priority
    />
  );
}

12.9 Custom Error Pages

Next.js allows you to create custom error pages for better user experience. You can create a custom 404.js and 500.js file in the pages directory.

Example: Creating a Custom 404 Page
// pages/404.js
export default function Custom404() {
  return <h1>404 - Page Not Found</h1>;
}

12.10 Head Management with next/head

Next.js provides a way to manage the <head> of your pages using the next/head component, allowing you to set titles, meta tags, and more.

Example: Using next/head
import Head from 'next/head';

function MyPage() {
  return (
    <>
      <Head>
        <title>My Next.js Page</title>
        <meta name="description" content="Description of my page" />
      </Head>
      <h1>Hello, Next.js!</h1>
    </>
  );
}

By exploring these bonus topics, you’ll enhance your knowledge of Next.js and be better equipped to build high-performance applications. This comprehensive roadmap will guide you through learning and mastering Next.js, empowering you to create robust web applications.


Read other awesome articles in Medium.com or in akcoding’s posts.

OR

Join us on YouTube Channel

OR Scan the QR Code to Directly open the Channel 👉

AK Coding YouTube Channel

Share with