Building Netflix Clone with NextJs 13.4: Part 1
Tags: nextjs, netflix, react, typescript
Published: 2023-12-19 04:19:57

### NextJs 13, React, Tailwind CSS, Firebase, Razorpay.

![Netflix Clone](https://miro.medium.com/v2/resize:fit:1000/format:png/1*9RQjGX9rUSFbU4NSTH6cyQ.png)

OTT platforms are the latest craze right now! With 238.39 million paid subscribers in 2023, Netflix is a fairly well-known OTT platform. This is the part 1 of the blog, where I’ll demonstrate how I created a Netflix clone using NextJs 13.4.

**Github Link:** [https://github.com/abhirupkumar/Netflix-Clone](https://github.com/abhirupkumar/Netflix-Clone/)

**Project Link:** [https://netflix-akb.vercel.app](https://netflix-akb.vercel.app/)

## Prerequisites

Knowledge of Html, Css, Js ,React and Nextjs.

## Getting started

Open your terminal and paste the below command to create the project.

    npx create-next-app@latest

Follow these steps for installation:

    What is your project named? netflix-clone
    Would you like to use TypeScript? Yes
    Would you like to use ESLint? No
    Would you like to use Tailwind CSS? Yes
    Would you like to use `src/` directory? No
    Would you like to use App Router? (recommended) Yes
    Would you like to customize the default import alias? No

After successfully creating the project, open the project in VScode and open it’s terminal. To install all the libraries needed for developing this project, start by typing the following command.

    npx install @emotion/react @emotion/styled @mui/material firebase razorpay react-hook-form react-hot-toast react-
    icons react-player recoil

Now go to **https://www.themoviedb.org/ **and Login/Sign Up. We will be using the TMDB API to get all the movies. Then go to [**https://www.themoviedb.org/settings/api](https://www.themoviedb.org/settings/api)** and get your API key. Create a .env.local file and paste your API key their.

    NEXT_PUBLIC_API_KEY=**********************

Now open a merchant account in Razorpay, get the merchant key and secret from your dashboard, and paste them in the .env.local.

    RAZORPAY_KEY=****************
    RAZORPAY_SECRET=****************

Before diving in Typescript code, write the following in global.css file.

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    /* :root {
      --foreground-rgb: 0, 0, 0;
      --background-start-rgb: 214, 219, 220;
      --background-end-rgb: 255, 255, 255;
    } */
    
    @media (prefers-color-scheme: dark) {
      /* :root {
        --foreground-rgb: 255, 255, 255;
        --background-start-rgb: 0, 0, 0;
        --background-end-rgb: 0, 0, 0;
      } */
    }
    
    body {
      overflow-x: hidden;
      /* color: rgb(var(--foreground-rgb));
      background: linear-gradient(
          to bottom,
          transparent,
          rgb(var(--background-end-rgb))
        )
        rgb(var(--background-start-rgb)); */
    }
    
    ::-webkit-scrollbar {
      width: 8px;
    }
    
    /* Track */
    ::-webkit-scrollbar-track {
      background: transparent;
    }
    
    /* Handle */
    ::-webkit-scrollbar-thumb {
      background: #222222;
    }
    
    /* Handle on hover */
    ::-webkit-scrollbar-thumb:hover {
      background: #363636;
    }
    
    .no-scrollbar::-webkit-scrollbar {
      display: none;
    }
    
    /* Hide scrollbar for IE, Edge and Firefox */
    .no-scrollbar {
      -ms-overflow-style: none;  /* IE and Edge */
      scrollbar-width: none;  /* Firefox */
    }
    
    @layer base {
      body {
        @apply bg-[#141414] text-white;
      }
    
      header {
        @apply fixed top-0 z-50 flex w-full items-center justify-between px-4 py-4 transition-all lg:px-10 lg:py-6;
      }
    }
    
    /* custom classNames */
    @layer components {
      .headerLink {
        @apply cursor-pointer text-sm font-semibold text-white transition duration-[.4s] hover:text-[#d6d6d6];
      }
    
      .bannerButton {
        @apply flex items-center gap-x-2 rounded px-5 py-1.5 text-sm font-semibold transition hover:opacity-75 md:py-2.5 md:px-8 md:text-xl;
      }
    
      .input {
        @apply w-full rounded bg-[#333] px-5 py-3.5 placeholder-[gray] outline-none focus:bg-[#454545];
      }
    
      .modalButton {
        @apply flex h-11 w-11 items-center justify-center rounded-full border-2 border-[gray] bg-[#2a2a2a]/60 transition hover:border-white hover:bg-white/10;
      }
    
      .planBox {
        @apply relative mx-1.5 flex h-20 w-[calc(100%/4)] cursor-default items-center justify-center rounded-sm bg-[#e50914] font-semibold shadow after:absolute after:top-full after:left-1/2 after:block after:-translate-x-1/2 after:border-8 after:border-b-0 after:border-transparent after:border-t-[#e50914] after:content-[""] md:h-32 lg:mx-8;
      }
    
      /* Table */
      .tableRow {
        @apply flex flex-wrap items-center font-medium;
      }
    
      .tableDataTitle {
        @apply w-full p-2.5 text-center text-sm font-normal text-white md:w-[40%] md:p-3.5 md:text-left md:text-base;
      }
    
      .tableDataFeature {
        @apply w-[calc(100%/4)] p-2.5 text-center md:w-[calc(60%/4)] md:p-3.5;
      }
    
      .membershipLink {
        @apply cursor-pointer text-blue-500 hover:underline;
      }
    
      /* MUI Menu */
      .menu {
        @apply md:hidden;
      }
    
      .menu .MuiPaper-root {
        @apply !absolute !left-0 !rounded-none !border !border-[gray] !bg-black !text-white;
      }
    
      .menu .MuiList-root {
        @apply !p-0;
      }
    
      .menu .MuiMenuItem-root {
        @apply !block !w-72 !py-3.5 !text-center !text-sm !font-light !text-[#b3b3b3] !transition !duration-200 first:cursor-default first:!font-normal first:!text-white hover:!bg-[#11100F];
      }
    }
    
    .lds-ripple {
      display: inline-block;
      position: relative;
      width: 80px;
      height: 80px;
      justify-content: center;
      align-items: center;
    }
    .lds-ripple div {
      position: absolute;
      border: 4px solid #ffffff;
      opacity: 1;
      border-radius: 50%;
      animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }
    .lds-ripple div:nth-child(2) {
      animation-delay: -0.5s;
    }
    @keyframes lds-ripple {
      0% {
        top: 36px;
        left: 36px;
        width: 0;
        height: 0;
        opacity: 0;
      }
      4.9% {
        top: 36px;
        left: 36px;
        width: 0;
        height: 0;
        opacity: 0;
      }
      5% {
        top: 36px;
        left: 36px;
        width: 0;
        height: 0;
        opacity: 1;
      }
      100% {
        top: 0px;
        left: 0px;
        width: 72px;
        height: 72px;
        opacity: 0;
      }
    }

Now create an action folder outside of the app directory. The folder will contain an actions.ts file. In action.ts, we will fetch all the movie data from the TMDB APIT.

    "use server";
    
    import requests from "@/utils/requests";
    
    export const fetchAllData = async () => {
      const [
        netflixOriginals,
        trendingNow,
        topRated,
        actionMovies,
        comedyMovies,
        horrorMovies,
        romanceMovies,
        documentaries,
      ] = await Promise.all([
        fetch(requests.fetchNetflixOriginals).then((res) => res.json()),
        fetch(requests.fetchTrending).then((res) => res.json()),
        fetch(requests.fetchTopRated).then((res) => res.json()),
        fetch(requests.fetchActionMovies).then((res) => res.json()),
        fetch(requests.fetchComedyMovies).then((res) => res.json()),
        fetch(requests.fetchHorrorMovies).then((res) => res.json()),
        fetch(requests.fetchRomanceMovies).then((res) => res.json()),
        fetch(requests.fetchDocumentaries).then((res) => res.json()),
      ]);
    
      return {
        netflixOriginals: netflixOriginals.results,
        trendingNow: trendingNow.results,
        topRated: topRated.results,
        actionMovies: actionMovies.results,
        comedyMovies: comedyMovies.results,
        horrorMovies: horrorMovies.results,
        romanceMovies: romanceMovies.results,
        documentaries: documentaries.results,
      };
    };

Before going any further, Login to Firebase and create a project. Give any project a name, and after creating the project, click on the web app, give the app a nickname, and click on Register the app. The following type of code will appear. Copy the code from their and click on done.

    // Import the functions you need from the SDKs you need
    import { initializeApp } from "firebase/app";
    // TODO: Add SDKs for Firebase products that you want to use
    // https://firebase.google.com/docs/web/setup#available-libraries
    
    // Your web app's Firebase configuration
    const firebaseConfig = {
      apiKey: "**************************************",
      authDomain: "*******************.firebaseapp.com",
      projectId: "*******************",
      storageBucket: "*******************.appspot.com",
      messagingSenderId: "*************",
      appId: "*:*************:web:*****************"
    };
    
    // Initialize Firebase
    const app = initializeApp(firebaseConfig);

Create a firebase.ts file outside of the app folder and paste the copied code. Again, go back to Firebase, click on Authentication, and turn on ‘Email/Password’ Authentication. Click on Firestore Database and create a database in production mode. Now we are all set to proceed with our hooks.

Create a constants folder outside of the app directory and create an app.tsx file and a movie.ts file in that folder.

    // movie.ts
    
    export const baseUrl = 'https://image.tmdb.org/t/p/original/';

    //App.tsx
    
    "use client";
    
    import React from 'react'
    import { RecoilRoot } from 'recoil'
    
    const App = ({
        children,
      }: {
        children: React.ReactNode
      }) => {
      return (
        <html lang="en">
          <body>
            <RecoilRoot>
              {children}
            </RecoilRoot>
          </body>
        </html>
      )
    }
    
    export default App;

In the app folder, change the code for layout.tsx to the following:

    import './globals.css'
    import type { Metadata } from 'next'
    import App from '@/constants/App';
    
    export const metadata: Metadata = {
      title: 'NETFLIX',
      description: 'Watch your favorite movies and TV shows on Netflix.',
    }
    
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
      return (
        <App children={children} />
      )
    }

Outside of app firectory create typings.d.ts file.

    // typings.d.ts
    
    export interface Genre {
        id: number
        name: string
      }
    
      export interface Plan {
        id: string
        name: string
        amount: number
        description: string
        videoQuality: string
        resolution: string
      }
      
      export interface Movie {
        title: string
        backdrop_path: string
        media_type?: string
        release_date?: string
        first_air_date: string
        genre_ids: number[]
        id: number
        name: string
        origin_country: string[]
        original_language: string
        original_name: string
        overview: string
        popularity: number
        poster_path: string
        vote_average: number
        vote_count: number
      }
      
      export interface Element {
        type:
          | 'Bloopers'
          | 'Featurette'
          | 'Behind the Scenes'
          | 'Clip'
          | 'Trailer'
          | 'Teaser'
      }

Now create an atoms folder outside of the app directory. The atoms folder will contain only one file, modalAtom.ts, which contains the following:

    import { DocumentData } from 'firebase/firestore'
    import { atom } from 'recoil'
    import { Movie } from '../typings'
    
    export const modalState = atom({
      key: 'modalState',
      default: false,
    })
    
    export const movieState = atom<Movie | DocumentData | null>({
      key: 'movieState',
      default: null,
    })

Create a utils folder outside of the app folder. In the utils folder, create data.ts and requests.ts files. The first file data.ts will contain our custom plan data. Feel free to change any items in the plan. And requests.ts file will contain all the API endpoints that we will be using in our application.

    // data.ts
    
    const plan = [
        {
            id: "netflix-plan-1-mobile",
            name: "Mobile",
            amount: 149,
            description: "Watch on your mobile phone and tablet",
            videoQuality: "Average",
            resolution: "480p"
        },
        {
            id: "netflix-plan-2-basic",
            name: "Basic",
            amount: 199,
            description: "Watch on your TV, computer, mobile phone and tablet",
            videoQuality: "Good",
            resolution: "720p"
        },
        {
            id: "netflix-plan-3-standard",
            name: "Standard",
            amount: 499,
            description: "Watch on your TV, computer, mobile phone and tablet",
            videoQuality: "Great",
            resolution: "720p"
        },
        {
            id: "netflix-plan-4-premium",
            name: "Premium",
            amount: 649,
            description: "Watch on your TV, computer, mobile phone and tablet",
            videoQuality: "Best",
            resolution: "720p"
        },
    ]
    
    export default plan

    // requests.ts
    
    const API_KEY = process.env.NEXT_PUBLIC_API_KEY
    const BASE_URL = 'https://api.themoviedb.org/3'
    
    const requests = {
      fetchTrending: `${BASE_URL}/trending/all/week?api_key=${API_KEY}&language=en-IN`,
      fetchNetflixOriginals: `${BASE_URL}/discover/movie?api_key=${API_KEY}&with_networks=213`,
      fetchTopRated: `${BASE_URL}/movie/top_rated?api_key=${API_KEY}&language=en-IN`,
      fetchActionMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-IN&with_genres=28`,
      fetchComedyMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-IN&with_genres=35`,
      fetchHorrorMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-IN&with_genres=27`,
      fetchRomanceMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-IN&with_genres=10749`,
      fetchDocumentaries: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-IN&with_genres=99`,
    }
    
    export default requests

Hence, we have set up all the utility files for our projects. In the next part, we will continue with the frontend and backend of our website.

*Guys, if you enjoyed this, please give it a clap and share it with your friends who also want to learn and implement Next.js 13. If you missed anything or want to [check out the full code here](https://github.com/abhirupkumar/Netflix-Clone). And if you want more articles like this, follow me on dev.to. [Link for Part 2](https://dev.to/abhirup2003/building-netflix-clone-with-nextjs-134-part-2-1khg). Thanks for reading!*