next.js

nextjs-13 -> code

npx create-next-app@13
# name, typescript, 

npm i bootstrap react-bootstrap nextjs-progressbar

npm run dev

folder

  • pages --> routing, (ssr,ssg,isr)

  • components --> ui

  • models --> interface

routing (pages folder)

  • pages/index.ts (default page) --> localhost:3000

  • pages/search.ts -->localhost:3000/search

    pages/api --> backend endpoint

    pages/_app.ts --> root page(similar to <App /> in react.js)

  •     // pages/_app.ts
    
        // <Head> --> for all title , meta, icon 
        // Inter --> local downloaded font from google
        // '@/styles/globals.css'; --> can only be used in this page only
    
        import 'bootstrap/dist/css/bootstrap.min.css';
        import '@/styles/globals.css';
        import { Inter } from '@next/font/google';
        import type { AppProps } from 'next/app';
        import Head from 'next/head';
        import { Container } from 'react-bootstrap';
        import styles from "@/styles/App.module.css";
        import NavBar from '@/components/NavBar';
        import NextNProgress from "nextjs-progressbar";
    
        const inter = Inter({ subsets: ['latin'] });
    
        export default function App({ Component, pageProps }: AppProps) {
          return (
            <div className={inter.className}>
              <Head>
                <title key="title">NextJS News App</title>
                <meta name="description" key="description" content="NextJS crash course by Coding in Flow" />
                <meta name="viewport" content="width=device-width, initial-scale=1" />
                <link rel="icon" href="/favicon.ico" />
              </Head>
              <NextNProgress />
              <NavBar />
              <Container className={styles.pageContainer}>
                <Component {...pageProps} />
              </Container>
            </div>
          );
        }
    

    .env.local --> root --> to keep key secret


ssr --> getServerSideProps

NewsArticleEntry --> single card

index -->NewsArticlesGrid --> map ->NewsArticleEntry

// models/NewArtical.ts
export interface NewsArticle {
    author: string,
    title: string,
    description: string,
    url: string,
    urlToImage?: string,
    publishedAt: string,
    content: string,
}

export interface NewsResponse {
    articles: NewsArticle[],   // ->[{author:,title:,...},{},{}] --> arry of NewsArticle object 
}
//pages/index.ts --> localhost:3000
//<Head> --> to overwrite the title of _app.ts page
import NewsArticlesGrid from '@/components/NewsArticlesGrid';
import { NewsArticle, NewsResponse } from '@/models/NewsArticles';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import { Alert } from 'react-bootstrap';

interface BreakingNewsPageProps {
  newsArticles: NewsArticle[],
}

//ssr ->getServerSideProps --> better seo,faster load
// return props , so that we can use it --> newsArticles
export const getServerSideProps: GetServerSideProps<BreakingNewsPageProps> = async () => {
  const response = await fetch("https://newsapi.org/v2/top-headlines?country=us&apiKey=" + process.env.NEWS_API_KEY);
  const newsResponse: NewsResponse = await response.json();
  return {
    props: { newsArticles: newsResponse.articles }
  }
  // let error go to 500 page
}

export default function BreakingNewsPage({ newsArticles }: BreakingNewsPageProps) {
  return (
    <>
      <Head>
        <title key="title">Breaking News - NextJS News App</title>
      </Head>
      <main>
        <h1>Breaking News</h1>
        <Alert>
          This page uses <strong>getServerSideProps</strong> to fetch data server-side on every request.
          This allows search engines to crawl the page content and <strong>improves SEO</strong>.
        </Alert>
        <NewsArticlesGrid articles={newsArticles} />
      </main>
    </>
  )
}
// components/NewsArticleEntry.ts

// ssr,isr,ssg --> cant be used in component 
//<Image /> --> lazy loading,height and width optimization
import { NewsArticle } from "@/models/NewsArticles";
import Image from "next/image";
import { Card } from "react-bootstrap";
import placeholderImage from "@/assets/images/newsarticle_placeholder.jpg";
import styles from "@/styles/NewsArticleEntry.module.css";

interface NewsArticleEntryProps {
    article: NewsArticle,
}

const NewsArticleEntry = ({ article: { title, description, url, urlToImage } }: NewsArticleEntryProps) => {

    const validImageUrl = (urlToImage?.startsWith("http://") || urlToImage?.startsWith("https://")) ? urlToImage : undefined;

    return (
        <a href={url}>
            <Card className="h-100">
                <Image
                    src={validImageUrl || placeholderImage}
                    width={500}
                    height={200}
                    alt="News article image"
                    className={`card-img-top ${styles.image}`}
                />
                <Card.Body>
                    <Card.Title>{title}</Card.Title>
                    <Card.Text>{description}</Card.Text>
                </Card.Body>
            </Card>
        </a>
    );
}

export default NewsArticleEntry;
// next.config.js --> to load image from this -> www.si.com
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ["www.si.com"],
    remotePatterns: [
      {
        protocol: "https",
        hostname: "**",
      },
      {
        protocol: "http",
        hostname: "**",
      },
    ]
  }
}

module.exports = nextConfig
// components/NewsArticlesGrid.ts

// responsive --> <Row xs={1} sm={2} xl={3} ></Row>
import { NewsArticle } from "@/models/NewsArticles";
import { Col, Row } from "react-bootstrap";
import NewsArticleEntry from "./NewsArticleEntry";

interface NewsArticlesGridProps {
    articles: NewsArticle[],
}

const NewsArticlesGrid = ({ articles }: NewsArticlesGridProps) => {
    return (
        <Row xs={1} sm={2} xl={3} className="g-4">
            {articles.map(article => (
                <Col key={article.url}>
                    <NewsArticleEntry article={article} />
                </Col>
            ))}
        </Row>
    );
}

export default NewsArticlesGrid;

csr

// pages/search.ts --> localhost:3000/search
//csr
//swr library can also be used
//<Head> --> to overwrite the title of _app.ts page
import NewsArticlesGrid from "@/components/NewsArticlesGrid";
import { NewsArticle } from "@/models/NewsArticles";
import Head from "next/head";
import { FormEvent, useState } from "react";
import { Alert, Button, Form, Spinner } from "react-bootstrap";

const SearchNewsPage = () => {
    const [searchResults, setSearchResults] = useState<NewsArticle[] | null>(null);
    const [searchResultsLoading, setSearchResultsLoading] = useState(false);
    const [searchResultsLoadingIsError, setSearchResultsLoadingIsError] = useState(false);

    async function handleSubmit(e: FormEvent<HTMLFormElement>) {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        const searchQuery = formData.get("searchQuery")?.toString().trim();

        if (searchQuery) {
            try {
                setSearchResults(null);
                setSearchResultsLoadingIsError(false);
                setSearchResultsLoading(true);
                const response = await fetch("/api/search-news?q=" + searchQuery);
                const articles: NewsArticle[] = await response.json();
                setSearchResults(articles);
            } catch (error) {
                console.error(error);
                setSearchResultsLoadingIsError(true);
            } finally {
                setSearchResultsLoading(false);
            }
        }
    }

    return (
        <>
            <Head>
                <title key="title">Search News - NextJS News App</title>
            </Head>
            <main>
                <h1>Search News</h1>
                <Alert>
                    This is page uses <strong>client-side data fetching</strong> to show fresh data for every search.
                    Requests are handled by our backend via <strong>API routes</strong>.
                </Alert>
                <Form onSubmit={handleSubmit}>
                    <Form.Group className="mb-3" controlId="search-input">
                        <Form.Label>Search query</Form.Label>
                        <Form.Control
                            name="searchQuery"
                            placeholder="E.g. politics, sports, ..."
                        />
                    </Form.Group>
                    <Button type="submit" className="mb-3" disabled={searchResultsLoading}>
                        Search
                    </Button>
                </Form>
                <div className="d-flex flex-column align-items-center">
                    {searchResultsLoading && <Spinner animation="border" />}
                    {searchResultsLoadingIsError && <p>Something went wrong. Please try again.</p>}
                    {searchResults?.length === 0 && <p>Nothing found. Try a different query!</p>}
                    {searchResults && <NewsArticlesGrid articles={searchResults} />}
                </div>
            </main>
        </>
    );
}

export default SearchNewsPage;
// pages/api/search-news.ts

import { NewsResponse } from '@/models/NewsArticles';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const searchQuery = req.query.q?.toString();

  if (!searchQuery) {
    return res.status(400).json({ error: "Please provide a search query" });
  }

  const response = await fetch(`https://newsapi.org/v2/everything?q=${searchQuery}&apiKey=${process.env.NEWS_API_KEY}`);
  const newsResponse: NewsResponse = await response.json();

  res.status(200).json(newsResponse.articles);
}

dynamic routing ->[] , (ssg -> getStaticPaths , isr --> revalidate: 5 * 60,) --> hell faster than ssr

//pages/categories/[category].ts --> localhost:3000/category/mamam
// useRouter --> router.query.category?.toString(); --> mamam

import NewsArticlesGrid from "@/components/NewsArticlesGrid";
import { NewsArticle, NewsResponse } from "@/models/NewsArticles";
import { GetStaticPaths, GetStaticProps } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { Alert } from "react-bootstrap";

interface CategoryNewsPageProps {
    newsArticles: NewsArticle[],
}

export const getStaticPaths: GetStaticPaths = async () => {
    const categorySlugs = [ // this could be coming from an API
        "business",
        "entertainment",
        "general",
        "health",
        "science",
        "sports",
        "technology",
    ];

    const paths = categorySlugs.map(slug => ({ params: { category: slug } }));

    return {
        paths,
        fallback: false,
    }
}

export const getStaticProps: GetStaticProps<CategoryNewsPageProps> = async ({ params }) => {
    const category = params?.category?.toString();
    const response = await fetch(`https://newsapi.org/v2/top-headlines?country=us&category=${category}&apiKey=${process.env.NEWS_API_KEY}`);
    const newsResponse: NewsResponse = await response.json();
    return {
        props: { newsArticles: newsResponse.articles },
        revalidate: 5 * 60,
    }
    // let error go to 500 page
}

const CategoryNewsPage = ({ newsArticles }: CategoryNewsPageProps) => {
    const router = useRouter();
    const categoryName = router.query.category?.toString();

    const title = "Category: " + categoryName;

    return (
        <>
            <Head>
                <title key="title">{`${title} - NextJS News App`}</title>
            </Head>
            <main>
                <h1>{title}</h1>
                <Alert>
                    This is page uses <strong>getStaticProps</strong> for very high page loading speed
                    and <strong>incremental static regeneration</strong> to show data not older than <strong>5 minutes</strong>.
                </Alert>
                <NewsArticlesGrid articles={newsArticles} />
            </main>
        </>
    );
}

export default CategoryNewsPage;

customize 404 page

const NotFoundPage = () => {
    return ( 
        <div>
            <h1>Not Found</h1>
            <p>Looks like this page does not exist!</p>
        </div>
     );
}

export default NotFoundPage;

customize 500 page

const ErrorPage = () => {
    return ( 
        <div>
            <h1>Error 😵</h1>
            <p>Looks like something went wrong. Please refresh the page or contact support!</p>
        </div>
     );
}

export default ErrorPage;

navbar

//components/NavBar.ts
// <Link> --> navigate between pages --> 
// as={Link} --> since i want link feature and nav ui of bootstrap

import Link from "next/link";
import { Container, Nav, Navbar, NavDropdown } from "react-bootstrap";

const NavBar = () => {
    return (
        <Navbar bg="dark" variant="dark" sticky="top" expand="sm" collapseOnSelect>
            <Container>
                <Navbar.Toggle aria-controls="main-navbar" />
                <Navbar.Collapse id="main-navbar">
                    <Nav>
                        <Nav.Link as={Link} href="/">Breaking</Nav.Link>
                        <Nav.Link as={Link} href="/search">Search</Nav.Link>
                        <NavDropdown title="Categories" id="categories-dropdown">
                            <NavDropdown.Item as={Link} href="/categories/business">Business</NavDropdown.Item>
                            <NavDropdown.Item as={Link} href="/categories/entertainment">Entertainment</NavDropdown.Item>
                            <NavDropdown.Item as={Link} href="/categories/general">General</NavDropdown.Item>
                            <NavDropdown.Item as={Link} href="/categories/health">Health</NavDropdown.Item>
                            <NavDropdown.Item as={Link} href="/categories/science">Science</NavDropdown.Item>
                            <NavDropdown.Item as={Link} href="/categories/sports">Sports</NavDropdown.Item>
                            <NavDropdown.Item as={Link} href="/categories/technology">Technology</NavDropdown.Item>
                        </NavDropdown>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>
    );
}

export default NavBar;

nextjs-14

seo in next.js14

migtate 13 to 14 --> /page to /app

ecommerce-nextjs 14