DEV Community

Cover image for Building Netflix Clone with NextJs 13.4: Part 1
Abhirup Kumar Bhowmick
Abhirup Kumar Bhowmick

Posted on • Edited on

Building Netflix Clone with NextJs 13.4: Part 1

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

Netflix Clone

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

Project Link: 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 and get your API key. Create a .env.local file and paste your API key their.

NEXT_PUBLIC_API_KEY=**********************
Enter fullscreen mode Exit fullscreen mode

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=****************
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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} />
  )
}
Enter fullscreen mode Exit fullscreen mode

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'
  }
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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. And if you want more articles like this, follow me on dev.to. Link for Part 2.
Guys, I have some good news for those who are preparing for interviews. Checkout PrepiQ (https://prepiq.vercel.app/), your comprehensive interview prep guide.
Thanks for reading!

Top comments (0)