Olibr Blogs

Blog > All Engineering Topics > How to Create a Full-Stack Application with Next.js?

How to Create a Full-Stack Application with Next.js?

by Pranisha Rai
Fullstack nextjs app cover
Pointer image icon

Introduction

Next.js project at first, might seem intimidating with many new concepts to grasp. But fear not! You will be completely blown away by its tons of benefits. These added benefits will accelerate your development process, and provide a better user experience. Eventually, working with Next.js will be a cakewalk. If you’re still overwhelmed in using Next.js, this article will help to understand the concept of it with the steps and code. Let’s dive right into its steps! 

Pointer image icon

Install Next.js and Getting Started with It

Install Next.js and Getting Started with It
  • First, you need to install the Next.js framework by simply writing the command “npm install next react react-dom” in the terminal. This will install dependencies such as React and React DOM into your system. 
  • Next, you need to set up your project in the Next.js app by running this command “npx create-next-app my-app”  
  • Once done, it will create a new app in a directory called “my-app”. You can also keep any name at your convenience instead of using “my-app”. 
  • Now, open the “my-app” directory and modify the default app that was created using the “create-next-app” command.  
  • You can make changes from your code editor, modify the content of the home page from the “page/index.js”, and add additional pages by creating new files in the “page” directory. 
  • You can use the command “npm run dev” to run the app, this will begin server development and then open your apps in the browser. 
  • So this is the step to get started with your Next.js app the next section walks you through the steps to build a homepage. 
Pointer image icon

Steps to Create a Navigation Bar and Shared Layout in Next.js

Unlike the other applications, Next.js comes with a shared layout concept that can be used in the entire application. One of these is a “Root Layout” that provides consistent structure in the application. Moreover, it includes all the necessary HTML tags.

Similarly, each route segment comes with its own layout that is shared across all the pages. This in return, maintains consistency and allows specific layouts for different sections in your app.

  • Open your code editor from there open the app layout file (app/layout.js) and then add the following code.
const inter = Inter({ subsets: ['latin'] })

export const metadata = {

  title: 'XYZ,

  description: 'Come here and learn more about Family Guy!',

}

export default function RootLayout({ children }) {

  return (

    <html lang="en">

      <body className={inter.className}>

        <Navigation />

        {children}

      </body>

    </html>

  )

}

So, you might wonder what the above code is doing exactly. Here’s the breakdown:

First, we define the “metadata” object in the component that contains all the default metadata tags in your application. These meta tags play a key role in helping your site rank better on search engines.

The “title property” specifies the title in your application.

The “description property” on the other hand, provides a brief description about that page.

If there is a requirement to change and modify this default metadata, you can easily adjust it according to your needs.

And we have structured the HTML tags inside the “RootLayout” function.

To set the language in English we have set the lang=”en” attribute.

The body tag includes “navigation component” and “children prop”, where the navigation component represents the shared across in the application and the children prop represents the content rendered in the “RootLayout component”.

  • Now open your navigation file and add the following code:
export const Navigation = () => {
return (
<div className="sticky top-0 backdrop-blur-xl bg-[rgba(0,0,0,0.8)] border-b border-slate-800 z-50">
<Container className="flex justify-between py-5">
<Link href="/">
<Image src="/logo.png" alt="Family Guy" width={70} height={50} />
</Link>
<Link
href="/quiz"
className="flex items-center justify-center gap-1 px-5 font-semibold text-black transition-colors bg-green-500 rounded-md duration-600 hover:bg-green-600"> 
<TbArrowBigRightFilled className="text-lg" />
XYZ
</Link>
</Container>
</div>
)
}

The above code will create and share the navigation layout across your application successfully, try and check on your local server. This will streamline the process of managing the elements in your Next.js app.

This code fetches data from the API endpoint for UI character data and then uses it to dynamically render the grid layout of the avatars with clickable links with individual character pages. But we haven’t used the “useEffect” hook in this code to fetch data from the API, still, this code will function undoubtedly.

Let’s break down its structure:
getAllCharacters”: This makes the asynchronous function HTTP request to the API endpoint and stores its response in the data variable. Next, it checks for the error whenever the HTTP response returns status code 200 then it throws an error.
Page Component”: It waits for the result from the calling function and stores data variables.
Main tag”: It holds a grid layout with multiple columns.“Containers”: It maps the character’s array in the data object and then generates the list of items. The “Link component” is created for each character that serves as a clickable link to a specific character page.
Slug property”: This will generate the URL links based on the character and within that, we have an image component to display the character’s avatar image.By now you will have an amazing-looking homepage. The next steps walk you through the steps to avoid code repetition to improve code reusability.

  • Now, go to the Page.jsx file and find the “getAllCharacters” function and then delete it.
  • After that, open the character.js file and export the “getAllCharacters” from there. This will import function into different parts of the codebase.
  • Now add the following code:
    Library.js fileimport { endpoint } from '@/utils/endpoint' 

    export async function getAllCharacters() { 

    const data = await fetch(`${endpoint}/characters`)  

    if (!data.ok) {   

    throw new Error('Failed to fetch data') 

    }  

    return data.json()

    }

    Page.jsx file
    import { getAllCharacters } from ‘@/lib/characters’
    export default async function Page() {  

    const data = await getAllCharacters()

    return (   
    <main>
     //content went here ...   
    </main> 
    )
    }

You have imported the “getAllCharacters” function from the library.js file to the index.jsx file. The next section walks you through the dynamic API route in Next.js to fetch character data based on the character slug.

Pointer image icon

Steps to Create Dynamic API Routes in Next.js

  • You need to use brackets in the folder name “[slug]/route.js” to indicate a dynamic route. This will allow retrieval and display of any character data.
  • Now open your API character [slug] route.js file and add the following code:
export async function GET(req, { params }) {  
try {   
const character = characters.data.find(item => item.slug === params.slug)

if (!character) { 
     return new NextResponse('not found', { status: 404 })   
}    
const character_qoutes = qoutes.data.filter(     
item => item.character_id === character.id,   
)    

return NextResponse.json({     
character,     
character_qoutes: character_qoutes.length > 0 ? character_qoutes : null,   
}) 
} catch (error) {   

return new NextResponse('Internal Server Error', { status: 500 }) 
  }
}

So what we did do? The code extracts dynamic parameters from the URL automatically and makes them available in the object “params”. It also uses the “params.slug” to access slug parameter and retrieves the specific character’s slug from the URL. Let’s break it down and understand.

  • The asynchronous function GET is responsible for managing GET requests.
  • We have used “character” and “quotes” from the JSON files using (@/data/characters.json and @/data/quotes.json , which is the Next.js file system.
  • req” and “params” contain the dynamic parameters extracted from the URL.
  • Try and catch” block compares the slug parameter from params within the slug property and try to find out each character object. If it does not find the character, it returns “not found 404” from the next/server package. And if it finds the character, code will filter the data array matching the “character_id” property.
  • And then the filter character quotes are assigned to the “character_quotes” variable.
  • At last the code uses “NextResponse.json()” “character” object and “character_quotes”to return JSON response.

Now you need to showcase this dynamic UI page by using the page.jsx file.Open character [slug] page.js file and add the following code:

import { getAllCharacters } from '@/lib/characters' 
export const dynamicParams = false

export async function generateStaticParams() { 
const { characters } = await getAllCharacters()
return characters.map(character => ({ slug: character.slug }))
}
export async function getCharacterBySlug(slug) { 
const data = await fetch(`${endpoint}/characters/${slug}`)

if (!data.ok) {    throw new Error('Failed to fetch data') 
}  
return data.json()
}

export default async function Page({ params }) {  const { character, character_qoutes } = await getCharacterBySlug(params.slug)

return (   
<Container className="flex flex-col gap-5 py-5" as="main">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold capitalize">{character.name}</h1>
<ul className="flex gap-1 text-sm">
{character.occupations.map(item => {           
return (
<li               
key={item}               
className="p-2 text-gray-300 bg-gray-800 rounded-md">
{item}             
</li>
)         
})}       
</ul>     
</div>

<p className="text-sm leading-6">{character.description}</p>     
<ul className="grid gap-2 sm:grid-cols-2">       
{character.images.map(image => {          
return (           
<li             
key={image}             
className="relative flex overflow-hidden bg-gray-900 rounded-xl">
<Image               
className="transition-all duration-500 hover:scale-110 hover:rotate-2"               
src={image}               
alt=""               
width={760}               
height={435}             
/>           
</li>         
)       
})}
</ul>
{character.skills && (       
<>
<h2 className="text-xl font-bold">Power and Skills</h2>         
<ul className="flex flex-wrap gap-1">           
{character.skills.map(item => {             
return (               
 <li                 
className="flex justify-center flex-grow px-2 py-1 text-orange-400 rounded-full bg-orange-950"                 
key={item}               
>                 
{item}               
</li>
              )           
})}         
</ul>       
</>     
)}
{character_qoutes && (       
<>         
<h2 className="text-xl font-bold">Famous Qoutes</h2>         
<ul className="grid gap-5">           
{character_qoutes.map((item, idx) => {             
return (
                <li                  
className="p-2 italic text-gray-400 border-l-4 border-green-400 rounded-md"                 
key={item.idx}               
>                 
{item.qoute}               
</li>
)
})}         
</ul>       
</>     
)}   
</Container> 
)
}

Now let’s understand what we did in this code, first, we used “generateStaticParams” to to get the dynamic path of objects that you want to pre-render during build time. This “getAllCharacters()” function maps over the character and returns each containing slug property with slug value. The “dynamicParamas” on the other hand, control the behavior of dynamic segments that were not generated using “generateStaticPramas”. When it’s set to “true”, Next.js will try to fetch the corresponding page. On the flip side, if its set to “false”, Next.js will return 404 pages.

The asynchronous function “getCharacterBySlug” uses the slug parameter and fetches the data from the specified API endpoint. If it returns data in JSON format successfully but if it does not, then it throws errors. The page component receives dynamic parameter values from the “prop” object. And calls for the “getCharacterBySlug” function to pass the character slug from “params”. It returns the name of the character, description, skills, power, occupation, and images.

By now, you have created dynamic API routes, layout, navigation, and the homepage in Next.js. Only the only thing is left to add interactivity to your Next.js application. This next section walks you through how to add interactivity to your app.

best software companies

Don't miss out on your chance to work with the best!

Apply for top job opportunities today!

Pointer image icon

Steps to Create API Route to Retrieve Random Question

First, open the random route.js file and add the following code:
export async function GET() { 
try {   
const random = Math.floor(Math.random() * questions.data.length)   
return NextResponse.json({     
randomQuestion: questions.data[random].id,   
})
} catch (error){   
return new NextResponse('Internal Server Error', { status: 500 }) 
}
}

This code implements the logic to fetch random questions stored in the JSON file. The “GET” function generates the data using two functions i.e., “Math.random()” and “Math.floor()” . Finally, the “id property” randomly selects the question.

Now, you will create the user interface of the quiz introduction section.

Open your quizpage. Jsx file and add the following:
export async function getRandomQuizQuestion() { 
const data = await fetch(`${endpoint}/quiz/random`, { cache: 'no-store' })
if (!data.ok) {   
throw new Error('Failed to fetch data') 
}
  return data.json()
}
export default async function Page() { 
const data = await getRandomQuizQuestion()
return (   
<Container     
as="main"
className="flex flex-col gap-5 py-5 md:flex-row-reverse md:justify-between">
<div className="relative overflow-hidden rounded-2xl">       
<div className="md:w-[24rem]">         
<Image src="/wallpaper.jpg" alt="" width={700} height={700} />       
</div>
<div className="absolute top-0 bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent md:bg-gradient-to-r"></div>     
</div>

<div className="md:w-[50%] flex flex-col gap-5">       
<h1 className="text-2xl font-semibold">Family Guy Quiz</h1>        
<p className="text-sm leading-6 text-gray-300">

Take this quiz to find out how much you know about the hit animated      sitcom Family Guy. Test your knowledge of the characters, the episodes, and the show&apos;s many pop culture references.
</p>
<Link         
href={`/quiz/${data.randomQuestion}`}
className="flex items-center justify-center gap-1 px-5 py-4 font-semibold text-orange-500 transition-colors rounded-md outline duration-600 hover:bg-orange-950">
<TbArrowBigRightFilled className="text-lg" />         
Take a Quiz Now!       
</Link>     
</div>   
</Container> 
)
}

The code above sets up the dynamic and interactive user interface for the quiz and fetches random questions from the API. Here, we have used the fetch method: { cache: ‘no-store’ }. which means it won’t use the static site generation method but instead uses the API request to fetch new data each time users visit the page. The use of “{ cache: ‘no-store‘ }” disables the cache and fetches a new question every time.

Pointer image icon

Dynamic API Routes Code for Quiz Answer using Next.js

export async function GET(req, { params }){  
try {   
const question = questions.data.find(item => item.id === params.id)
if (!question) {     
return new NextResponse('not found', { status: 404 })   
}
const { correct_answer, ...rest } = question
return NextResponse.json({     
question: rest,   
})
} catch (error) {   
return new NextResponse('Internal Server Error', { status: 500 }) 
}
}
Pointer image icon

Dynamic Server-Side Rendering Code in Next.js

export async function GET(req, { params }) {  
try {   
const question = questions.data.find(item => item.id === params.id)
    if (!question) {
      return new NextResponse('not found', { status: 404 })   
}
const { correct_answer } = question
    const filteredQuestions = questions.data.filter(     
item => item.id !== params.id,   
)
const random = Math.floor(Math.random() * filteredQuestions.length)
return NextResponse.json({     
correct: correct_answer,     
random: filteredQuestions[random].id,   
})
} catch (error) {    return new NextResponse('Internal Server Error', { status: 500 }) 
}
}
Pointer image icon

Client-Side Component Code using Next.js

'use client'
import { useEffect, useState } from 'react'
import cn from 'classnames'
import Link from 'next/link'
import { FiRepeat } from 'react-icons/fi'
import { MdNearbyError } from 'react-icons/md'
import { FaCheck } from 'react-icons/fa'

export const Answer = ({ answers, questionId }) => {
const [selected, setSeleceted] = useState(null)
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)

useEffect(() => {
let subscribed = true
if (selected) {
setLoading(true)
fetch(`/api/quiz/answer/${questionId}`)
.then(res => res.json())
.then(data => {
setLoading(false)
if (subscribed) {
setData(data)
}
})
}

return () => {
console.log('cancelled!')
subscribed = false
}
}, [questionId, selected])

return (
<>
<ul className="grid grid-cols-2 gap-2 md:grid-cols-4">
{answers.map(item => {
const isLoading = selected === item && loading
const isWrong =
selected === item && data && data?.correct !== selected
const isCorrect = data?.correct === item

return (
<li key={item}>
<button
disabled={data || loading}
onClick={() => setSeleceted(item)}
className={cn(
'p-2 rounded-md items-center justify-between w-full flex text-sm font-semibold disabled:cursor-not-allowed transition-all',
isLoading && 'animate-pulse',
isWrong ? 'bg-red-700' : 'bg-slate-800',
isCorrect && 'outline text-green-500',
)}
>
{item}
{isCorrect && <FaCheck />}
{isWrong && <MdNearbyError />}
</button>
</li>
)
})}
</ul>
{data?.random && (
<Link
href={`/quiz/${data.random}`}
className="flex items-center gap-1 text-blue-400"
>
<FiRepeat className="mt-1" />
Do it again
</Link>
)}
</>
)
}
Pointer image icon

Final Words

Next.js offers robust features for both front-end and back-end development. You can keep practicing and exploring to unleash the power of Next.js. I hope by following the above-mentioned steps and code, you can create highly scalable apps.

Take control of your career and land your dream job!

Sign up and start applying to the best opportunities!

FAQs

It may not be suitable for simple projects that don’t necessarily need features such as dynamic routing and server-side rendering.

Yes! It allows client-side rendering as well as static site generation. You can use Next.js to create a front-end development.

Yes, it can be integrated with Node.js to manage the server-side functionality and then efficiently enable full-stack development.

Next.js comes with many advanced features automatic static optimization, server rendering, and API routes. Whereas, Create React App, is a simpler tool and needs a basic development environment.

Absolutely, yes! Next.js supports both, which means developers have the option to choose the language of their preference.

React.js framework offers diverse options each one is tailored to specific development. All of these frameworks have their pros and cons, the best one depends on your project-specific needs. Whether you prioritize server-side rendering, or looking for a superfast, UI design, or form handling, there’s a React framework ready to assist you.  So with this, we have come to the end of the 16 Best React.js Frameworks in your development process that will not only elevate your project quality but also enhance productivity.

You may also like

Leave a Comment