coders gyan(Next.js) ---> book library(2.1)
npx create-next-app@latest
what is your project name? --> elib-client-app
would you like to use Typescript? --> yes
would you like to use Eslibt? --> yes
tailwind css? --> yes
src directory? --> yes
App Router --> yes
customize the default import alias? --> no
cd elib-client-app
npm run dev
file routing →(create folder and page.tsx)
src/app/page.tsx → / → home page
src/app/about/page.tsx → /about → about page
src/app/users/contact/page.tsx → /users/contact →contact page
dynamic file routing →[]
src/app/book/[bookId]/page.tsx →/book/23123 → single book page
() → not act as file routing
Layout
app/layout.tsx → automatically wrap all pages.tsx → {children}
→ so common(repated ui is placed here) —> Nav,Footer,Header
//app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
const inter = Inter({ 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={inter.className}>
<Navbar />
{children}
<Footer />
</body>
</html>
);
}
//src/components/Navbar.tsx
import Link from 'next/link';
import React from 'react';
const Navbar = () => {
return (
<nav className="border-b">
// max-w-7xl or container
<div className="max-w-7xl mx-auto flex items-center justify-between py-4">
<div>
<Link href={'/'}>
<div className="flex items-center gap-1">
<div className="relative">
<Hexagon />
<BookIcon />
</div>
<span className="text-xl font-bold uppercase tracking-tight text-primary-500">
Coders Book
</span>
</div>
</Link>
</div>
<div className="flex items-center gap-4">
<button className="h-10 rounded-md border border-primary-500 px-4 py-2 text-sm font-medium text-primary-500 transition-all hover:border-primary-100 hover:bg-primary-100 active:border-primary-200 active:bg-primary-200">
Sign in
</button>
<button className="h-10 rounded-md bg-primary-500 px-4 py-2 text-sm font-medium text-white transition-all hover:bg-primary-600 active:bg-primary-700">
Sign up
</button>
</div>
</div>
</nav>
);
};
export default Navbar;
const Hexagon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="45"
height="45"
viewBox="0 0 24 24"
fill="#ce7041"
stroke="#ce7041"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-hexagon">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
</svg>
);
const BookIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="#fff"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="#ce7041"
className="absolute left-1/2 top-1/2 h-8 w-8 -translate-x-1/2 -translate-y-1/2 transform">
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
/>
</svg>
);
// tailwind.config.ts
//
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
// primary color added
colors: {
primary: {
50: '#fcf6f0',
100: '#f7eadd',
200: '#eed2ba',
300: '#e3b28e',
400: '#d78d60',
500: '#ce7041',
600: '#bf5a36',
700: '#9f462f',
800: '#803a2c',
900: '#683226',
950: '#381712',
},
},
},
},
plugins: [],
};
export default config;
src/components →common or shared component for complete ui
src/app/(home)/components→ component used by home page only
//src/app/(home)/components/Banner.tsx
import React from 'react';
import Image from 'next/image';
const Banner = () => {
return (
<div className="mx-auto max-w-7xl py-10">
<div className="relative">
<Image
src={'/paper-bg.jpg'} // taking img from public folder
alt="billboard"
className="h-72 w-full rounded-lg"
height={0}
width={0}
sizes="100vw"
/>
<div className="absolute inset-0 h-full w-full rounded-lg bg-gray-950 opacity-30" />
<Image
src={'/book.png'}
alt="billboard"
className="absolute bottom-0 right-5"
height={0}
width={0}
sizes="100vw"
style={{ width: 'auto', height: '18rem' }}
/>
<h3 className="absolute left-10 top-1/2 w-full max-w-3xl -translate-y-1/2 text-5xl font-semibold tracking-tight text-white">
Connect, Share and Trade Your Favourite Reads...
</h3>
</div>
</div>
);
};
export default Banner;
//src/app/(home)/components/Banner.tsx
import React from 'react';
import Image from 'next/image';
const Banner = () => {
return (
<div className="mx-auto max-w-7xl py-10">
<div className="relative">
<Image
src={'/paper-bg.jpg'}
alt="billboard"
className="h-72 w-full rounded-lg"
height={0}
width={0}
sizes="100vw"
/>
<div className="absolute inset-0 h-full w-full rounded-lg bg-gray-950 opacity-30" />
<Image
src={'/book.png'}
alt="billboard"
className="absolute bottom-0 right-5"
height={0}
width={0}
sizes="100vw"
style={{ width: 'auto', height: '18rem' }}
/>
<h3 className="absolute left-10 top-1/2 w-full max-w-3xl -translate-y-1/2 text-5xl font-semibold tracking-tight text-white">
Connect, Share and Trade Your Favourite Reads...
</h3>
</div>
</div>
);
};
export default Banner;
//src/types/index.ts
export type Book = {
_id: string;
title: string;
description: string;
coverImage: string;
file: string;
author: Author;
};
export type Author = {
name: string;
};
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'res.cloudinary.com',
},
],
},
};
export default nextConfig;
//src/app/(home)/components/BookCard.tsx
import { Book } from '@/types';
import Image from 'next/image';
import Link from 'next/link';
import React from 'react';
const BookCard = ({ book }: { book: Book }) => {
return (
<div className="flex gap-5 border p-5 shadow-md rounded">
<Image
src={book.coverImage}
alt={book.title}
width={0}
height={0}
sizes="100vw"
style={{ width: 'auto', height: '12rem' }}
/>
<div>
<h2 className="line-clamp-2 text-xl font-bold text-primary-600 text-balance">
{book.title}
</h2>
<p className="font-bold text-primary-900 mt-1">{book.author.name}</p>
<Link
href={`/book/${book._id}`}
className="py-1 px-2 rounded border border-primary-500 mt-4 inline-block text-primary-500 font-medium text-sm
hover:border-primary-100 hover:bg-primary-100 transition">
Read more
</Link>
</div>
</div>
);
};
export default BookCard;
//src/app/(home)/components/BookList.tsx
import React from 'react';
import BookCard from './BookCard';
import { Book } from '@/types';
const BookList = async () => {
// data fetching -> always use fetch in next.js
const response = await fetch(`${process.env.BACKEND_URL}/books`, { cache: 'no-store' });
if (!response.ok) {
throw new Error('An error occurred while fetching the books');
}
const books = await response.json();
return (
<div className="grid grid-cols-1 gap-8 md:grid-cols-3 max-w-7xl mx-auto mb-10">
{books.map((book: Book) => (
<BookCard key={book._id} book={book} />
))}
</div>
);
};
export default BookList;
2 types of component?
client component →data fetching , parsing data to create html file is done on client side
server component → data fetching , parsing data to create html file is done on server side → faster file load, Seo
in next.js → by default all pages and layout are server component
“use client“ → to make server component to client component
// src/app/(home)/pages.tsx
import Banner from '@/app/(home)/components/Banner';
import Image from 'next/image';
import BookList from './components/BookList';
import { Suspense } from 'react';
import Loading from '@/components/Loading';
// export const dynamic = 'force-dynamic';
export default async function Home() {
return (
<>
<Banner />
<Suspense fallback={<Loading />}>
<BookList />
</Suspense>
</>
);
}