WebNest
Team/Shahnawaz Sazid/Next-Js-Core-Concept-Part-1

Repository

Next-Js-Core-Concept-Part-1

View on GitHub ↗
TypeScript0 stars0 forks

README

Next Js Core Concept Part-1

In this module, we’ll dive into the core concepts of Next.js to take your skills to the next level. You’ll learn how to optimize images, fonts, and metadata for better performance and SEO, explore pre-rendering strategies like SSG, SSR, and ISR, and practice advanced data fetching techniques. We’ll also cover global error handling, active links, and wrap up with hands-on tasks to solidify your learning.

52-1 Optimize image using Image Component

import Image from "next/image";
import nextImg from "../../../assets/31343C.svg"; // for local
const GalleryPage = () => {
  return (
    <div className="text-center">
      <h1>Regular Image Tag</h1>
      <img
        src="https://placehold.co/600x400"
        alt=""
        width={500}
        height={500}
        className="mx-auto"
      />

      <h1>Next.js image Component</h1>

      <Image
        src="https://placehold.co/600x400/png"
        alt="next image"
        width={500}
        height={500}
        className="mx-auto"
      />

      <h1>Local image Component</h1>

      {/* for local width and height is not mandatory actually  */}
      <Image src={nextImg} alt="next image" className="mx-auto" />

      <h1>From Public Local</h1>

      <Image
        src="/31343C.svg"
        alt="next image"
        width={500}
        height={500}
        className="mx-auto"
      />
    </div>
  );
};

export default GalleryPage;
  • for the Image component we have to setup next.config.mjs
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  // images: {
  //   remotePatterns: [new URL('https://placehold.co/600x400/png')],
  // },
  images: {
    remotePatterns: [
      {
        protocol: "https",
        // hostname: 's3.amazonaws.com', // for grabbing image from trusted sources only
        hostname: "**", // this allows images from, all sources
        // port: '',
        // pathname: '/my-bucket/**',
        // search: '',
      },
    ],
  },
};

export default nextConfig;

52-2 Optimize Fonts, Improve SEO with Metadata, and Use Import Aliases

  • By default next js uses fonts which comes from next/font/google
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <main className="min-h-dvh">{children}</main>
      </body>
    </html>
  );
}
  • here default variable font is used. lets use the font now

alt text

  • using the font
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={geistMono.className}>
        <main className="min-h-dvh">{children}</main>
      </body>
    </html>
  );
}
  • using roboto font in entire site
import type { Metadata } from "next";
import { Geist, Geist_Mono, Roboto } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

const roboto = Roboto({
  weight: "400",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        // className={geistMono.className}
        className={roboto.className}
      >
        <main className="min-h-dvh">{children}</main>
      </body>
    </html>
  );
}

If we want to use different font in a specific place

  • Load fonts in layout.tsx: import Google fonts with both className (for global default) and variable (so you can selectively apply them). Example:
  • inside the layout.tsx
const roboto = Roboto({ variable: "--font-roboto", subsets: ["latin"] });
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
  • Then set <body className={${roboto.variable} ${geistSans.variable}}>.
  • Expose them in CSS (optional but clean): in globals.css, map font variables to utility classes:
.font-roboto { font-family: var(--font-roboto); }
.font-geist-sans { font-family: var(--font-geist-sans); }

  • Use fonts selectively in components:
    • Default site font comes from body (roboto).
    • For special cases, apply class on elements:
<h2 className="font-geist-sans">Card Title</h2>
<p className="font-roboto">Card description</p>

Lets talk about the Meta data

  • in layout.tsx file default meta data is given
import type { Metadata } from "next";
import { Geist, Geist_Mono, Roboto } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

const roboto = Roboto({
  weight: "400",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        // className={geistMono.className}
        className={roboto.className}
      >
        <main className="min-h-dvh">{children}</main>
      </body>
    </html>
  );
}
  • but we want dynamic
  • lets take it the about page should have different meta data
import { Metadata } from "next";
import styles from "./About.module.css";

export const metadata: Metadata = {
  title: "About Page",
  description: "This is about page",
};

const AboutPage = () => {
  return (
    <div>
      <h1 className={styles.text_style}>About Page</h1>
    </div>
  );
};

export default AboutPage;
  • like this we can set static meta data we can generate dynamic meta data and set og images

lets talk about import alias

  • relative path import
import nextImg from "../../../assets/31343C.svg"
  • absolute path import
import nextImg from "@/assets/31343C.svg"

52-3 Explain core concept of pre-rendering (SSG + SSR)

alt text

  • pre rendering performance optimization is done by using static site generation (ssg) and server side rendering(ssr)

SSG- Static Site Generation

  • when we build a project and then we host the project maybe in vercel
  • In the initial state of deployment we make a build folder
  • Next.js makes the static content HTML bundle in the build time and the files are deployed in hosting server
  • when we hit a route, from the hosting server the html content will come to browser and will be shown to user

Server side rendering

  • In the same way project is build and hosted in hosting server.
  • Suppose user want to see a dynamic data like post whose id is 1 (dynamic).
  • first user will hit then server will take the request and search in database
  • when data is found the database will send it to server and server will generate html file using the data info and then will send to the user as response.
  • both SSG and SSR is handled by server but SSG is faster than ssr since in ssg the data is static and already made in build time.

52-4 Set-up Json Server for Next Level Data Fetching

  • we will create a dummy server in our project and do these.
  • for creating a dummy server we will use json-server npm in our project json-server-npm
  • install
npm i json-server
  • root folder -> db.json
{
  "products": [
    {
      "id": "1",
      "product_name": "Aegis Pro 15",
      "category": "Laptop",
      "brand": "PowerCore",
      "price": 1999,
      "image": "https://picsum.photos/seed/laptop1/400/300"
    },
    {
      "id": "2",
      "product_name": "Nimbus X1",
      "category": "Smartphone",
      "brand": "Skyline",
      "price": 899,
      "image": "https://picsum.photos/seed/phone2/400/300"
    },
    {
      "id": "3",
      "product_name": "Pulse Studio Over-Ear",
      "category": "Headphones",
      "brand": "SoundRipple",
      "price": 149,
      "image": "https://picsum.photos/seed/headphones3/400/300"
    },
    {
      "id": "4",
      "product_name": "Verta 27\" IPS",
      "category": "Monitor",
      "brand": "Viewark",
      "price": 329,
      "image": "https://picsum.photos/seed/monitor4/400/300"
    },
    {
      "id": "5",
      "product_name": "Quill Mechanical",
      "category": "Keyboard",
      "brand": "KeyCrafters",
      "price": 119,
      "image": "https://picsum.photos/seed/keyboard5/400/300"
    },
    {
      "id": "6",
      "product_name": "SwiftTrack Pro",
      "category": "Mouse",
      "brand": "GlideWorks",
      "price": 59,
      "image": "https://picsum.photos/seed/mouse6/400/300"
    },
    {
      "id": "7",
      "product_name": "Bolt NVMe 1TB",
      "category": "SSD",
      "brand": "FlashForge",
      "price": 129,
      "image": "https://picsum.photos/seed/ssd7/400/300"
    },
    {
      "id": "8",
      "product_name": "Halo AX6000",
      "category": "Router",
      "brand": "NetPulse",
      "price": 199,
      "image": "https://picsum.photos/seed/router8/400/300"
    },
    {
      "id": "9",
      "product_name": "SlatePad 10",
      "category": "Tablet",
      "brand": "Tabra",
      "price": 449,
      "image": "https://picsum.photos/seed/tablet9/400/300"
    },
    {
      "id": "10",
      "product_name": "Orion Mirrorless",
      "category": "Camera",
      "brand": "LumaTech",
      "price": 1199,
      "image": "https://picsum.photos/seed/camera10/400/300"
    },
    {
      "id": "11",
      "product_name": "Tempo Classic",
      "category": "Smartwatch",
      "brand": "Wristly",
      "price": 199,
      "image": "https://picsum.photos/seed/watch11/400/300"
    },
    {
      "id": "12",
      "product_name": "InkJet Pro M404",
      "category": "Printer",
      "brand": "Printiva",
      "price": 179,
      "image": "https://picsum.photos/seed/printer12/400/300"
    },
    {
      "id": "13",
      "product_name": "ArcLight Desk Lamp",
      "category": "Accessory",
      "brand": "LumiDesk",
      "price": 39,
      "image": "https://picsum.photos/seed/lamp13/400/300"
    },
    {
      "id": "14",
      "product_name": "Guardian UPS 1500VA",
      "category": "Power",
      "brand": "EnerSafe",
      "price": 259,
      "image": "https://picsum.photos/seed/ups14/400/300"
    },
    {
      "id": "15",
      "product_name": "CineRing LED Panel",
      "category": "Lighting",
      "brand": "ProLite",
      "price": 89,
      "image": "https://picsum.photos/seed/light15/400/300"
    }
  ]
}
  • as our frontend is running in port 3000 we will run the dummy server in different . lets add a script for it
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack",
    "start": "next start",
    "lint": "eslint",
    "json-server" : "json-server --watch db.json --port 5000"
  },
  • now run
npm run json-server

Now Lets see data fetching in next.js

  • next.js provides fetch method
  • and data fetching will be done in only server component and the component must be converted in async component
import ProductCard from "@/components/products/ProductCard";
import { IProduct } from "@/type";
import React from "react";

const ProductsPage = async () => {
  const res = await fetch("http://localhost:5000/products");
  const products = await res.json();

  console.log(products);

  return (
    <div>
      <h1 className="text-center">All Products!</h1>

      <div className="grid grid-cols-3 gap-4 w-[90%] mx-auto">
        {products.map((product: IProduct) => (
          <ProductCard key={product?.id} product={product} />
        ))}
      </div>
    </div>
  );
};

export default ProductsPage;

52-5 Explain SSG by Next-level Data Fetching

  • now lets see the caching while fetching. we will use forced-cache. this will store the caching like a cdn after first load.
  • another facility is in the build time it will generate htm with the fetched data and user will get the content instantly
import ProductCard from "@/components/products/ProductCard";
import { IProduct } from "@/type";
import React from "react";

const ProductsPage = async () => {
  const res = await fetch("http://localhost:5000/products", {
    cache: "force-cache",
  });
  const products = await res.json();

  console.log(products);

  return (
    <div>
      <h1 className="text-center">All Products!</h1>

      <div className="grid grid-cols-3 gap-4 w-[90%] mx-auto">
        {products.map((product: IProduct) => (
          <ProductCard key={product?.id} product={product} />
        ))}
      </div>
    </div>
  );
};

export default ProductsPage;
  • while doing build npm run build we have to keep the server running.
  • npm start

52-6 Breaking the Limits of Static Sites with Next.js ISR

  • For static method once the static html is generated further changes in Html do not reflect in Ui. For showing the new data we have to rebuild and redeploy again.
  • Next.js has solve this using Incremental Static Regeneration(ISR). This will call the fetch method after some time interval amd if any data changes it will rebuild and redeploy. This is Done automatically.
const res = await fetch("http://localhost:5000/products", {
  next: {
    revalidate: 5,
  },
});
import ProductCard from "@/components/products/ProductCard";
import { IProduct } from "@/type";
import React from "react";

const ProductsPage = async () => {
  const res = await fetch("http://localhost:5000/products", {
    next: {
      revalidate: 5,
    },
  });
  const products = await res.json();

  console.log(products);

  return (
    <div>
      <h1 className="text-center">All Products!</h1>

      <div className="grid grid-cols-3 gap-4 w-[90%] mx-auto">
        {products.map((product: IProduct) => (
          <ProductCard key={product?.id} product={product} />
        ))}
      </div>
    </div>
  );
};

export default ProductsPage;
  • which ever fetch data changes will be revalidated (ISR) and changed after 5 second
  • we can re validate tags as well
const res = await fetch("http://localhost:5000/products", {
  next: {
    tags: ["products"],
  },
});
  • this is better to use tags so that less network is used.

52-7 Advance Data Fetching Strategies with SSR

const res = await fetch("http://localhost:5000/products", {
  cache: "no-store",
});
  • the purpose of no-store is based on the user request html file will b ebuild in server and then come to us. It will not generate html file in build time. Thi is called server side rendering

alt text

  • as it will take time while loading we can show loading state.
  • in static site generation there is no chance to show the loading state by the way as user will be getting instant.
  • In case of ISR is also same as SSR and ISR is almost same. works done in build time.

Lets show loading state

  • app -> (commonLayout) - > products -> loading.tsx
const ProductsLoadingPage = () => {
  return (
    <div className="min-h-dvh flex justify-center items-center">
      <h1>Loading.........</h1>
    </div>
  );
};

export default ProductsLoadingPage;
import ProductCard from "@/components/products/ProductCard";
import { IProduct } from "@/type";
import React from "react";

const ProductsPage = async () => {
  const res = await fetch("http://localhost:5000/products", {
    cache: "no-store",
  });
  const products = await res.json();

  console.log(products);

  return (
    <div>
      <h1 className="text-center">All Products!</h1>

      <div className="grid grid-cols-3 gap-4 w-[90%] mx-auto">
        {products.map((product: IProduct) => (
          <ProductCard key={product?.id} product={product} />
        ))}
      </div>
    </div>
  );
};

export default ProductsPage;
  • If we want to show the loading screen where some data fetch is done we can create the loading.tsx in home page directory as well

  • app -> (commonLayout) - > products -> error.tsx

"use client";
const ErrorPage = () => {
  return (
    <div>
      <h1 className="text-center text-red-500 text-xl">Something went wrong</h1>
    </div>
  );
};

export default ErrorPage;
  • Error boundaries will be based on the products error.

  • Do not forget to convert into client component

  • Lets make the error page dynamic

"use client";

import { useEffect } from "react";

const ErrorPage = ({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) => {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);
  return (
    <div>
      <h1 className="text-center text-red-500 text-xl">Something went wrong</h1>
      <p>{error?.message}</p>

      <button
        onClick={() => reset()}
        className="mt-3 w-full bg-blue-500 text-white py-2 rounded-xl hover:bg-blue-600 mx-auto"
      >
        Try Again
      </button>
    </div>
  );
};

export default ErrorPage;
  • reset button will be used if any error occurs.

52-9 Handling Active Links in Next.js

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React from "react";

export default function Navbar() {
  const pathname = usePathname();

  const links = [
    { href: "/dashboard", label: "Dashboard" },
    { href: "/products", label: "Products" },
    { href: "/about", label: "About" },
    { href: "/gallery", label: "Gallery" },
    { href: "/contact", label: "Contact" },
    { href: "/login", label: "Login" },
  ];

  return (
    <nav className="w-full bg-gray-900 text-white px-6 py-3 flex items-center justify-between">
      {/* Logo / Brand */}
      <div className="text-xl font-bold">
        <Link href="/">NextJs</Link>
      </div>

      {/* Links */}
      <div className="flex space-x-6">
        {links.map((link) => {
          const isActive = pathname === link.href;

          return (
            <Link
              key={link.href}
              href={link.href}
              className={`${
                isActive
                  ? "text-yellow-400 font-semibold border-b-2 border-yellow-400"
                  : "hover:text-gray-300"
              }`}
            >
              {link.label}
            </Link>
          );
        })}
      </div>
    </nav>
  );
}
← Back to profile