- Lab
- Core Tech

Guided: Foundations of Next.js 14
Take your React skills to the next level with this 45-minute guided codelab on Next.js 14. Designed for developers with basic React and JavaScript knowledge, this session will introduce you to the powerful features of Next.js, including server-side rendering, static site generation, and dynamic routing. You'll build a simple, functional web app, exploring file-based routing, data fetching, and creating API routes. By the end, you'll have a solid foundation to build scalable, production-ready React applications with Next.js.

Path Info
Table of Contents
-
Challenge
1: Set up the Project
Objective
Get familiar with the Next.js project structure.
Welcome to this guided codelab where you'll build a web application for GloboTicket, an online ticket sales platform, using Next.js 14. ### 1. Project Initialization (Offline Setup)
This codelab already includes a basic Next.js project setup. If you want to set up a project from scratch in your local environment, you can use the command
npx create-next-app@latest globoticket
, but don't run it in this codelab environment, as the project is already set up for you.NOTE: Tests in the
.tests
folder are for verification of the tasks. Do not edit these. You should not need to edit thepackage.json
file either. If you ever get stuck, follow the steps or check the solutions. Thesolution
folder contains the files at the end state of the codelab, feel free to check them as well. Lastly, you might need to re-run the dev server withnpm run dev
if it starts throwing errors or does not pick up the changes after you edit your files. ### 2. Project StructureNext.js projects have a specific structure. Here's an overview of the key folders and files, do not worry if you don't understand everything yet, we will cover each part in detail in the following steps:
app/
: This is the main directory for the Next.js project using the App Router introduced in Next.js 13 and above. It contains the application's pages and components. Next.js uses file-based routing, so each subfolder corresponds to a route in your app.app/layout.tsx
: The root layout component that wraps all pages. It's used for shared components like headers and footers.app/page.tsx
: The default page for the root route/
. This is the Home page of your application.app/global.css
: The global CSS file applied to your application.app/navbar.module.css
: A CSS module file for the Navbar component that we'll create later.app/util/
: Contains utility functions and helpers used throughout the application. In this case it will provide us with mock data. This folder does not correspond to a route since it doesn't contain apage.tsx
file.app/util/events.ts
: Contains a list of events that will be used in the application.
public/
: Contains static assets like images, fonts, and other files that need to be served directly..tests/
: Contains automated tests that verify the completion of tasks in each step. Do not edit these files.package.json
: Lists the project's dependencies and scripts. You should not need to edit this file.
Note: The project uses TypeScript, so files have the
.tsx
extension for components and pages. Don't worry if you are not familiar with TypeScript, you can think of it as JavaScript with types, but it's use is not the focus of this codelab. ### 3. Run the Development ServerLet's ensure that the project is set up correctly.
In the terminal, start the development server:
npm run dev
This command should start the Next.js development server and make your application available at http://localhost:3000. Open the "Web browser" tab and navigate to http://localhost:3000. You should see the initial content of the GloboTicket application, for now this is just a Hello world example.
-
Challenge
2: Create the Home Page
Objective
Understand Next.js metadata, rendering as React components, and create the Home page for GloboTicket.
-
Challenge
3: Create the Events and Eventdetail Pages
Objective
Understand file-based routing in Next.js and create the Events and EventDetail pages for GloboTicket.
-
Challenge
4: Add Information to the Events and Eventdetail Pages
Objective
Understand how to use components and props in Next.js and add information to the Events and EventDetail pages for GloboTicket.
-
Challenge
5: Link Component
Objective
Understand how to use the Link component in Next.js and create links between pages.
-
Challenge
6: Image Component
Objective
Understand how to use the Image component in Next.js and optimize images for performance.
-
Challenge
7: Create the Layout Component
Objective
Understand how to create a Layout component in Next.js and use it to wrap pages.
-
Challenge
8: Style the Layout - CSS and Fonts
Objective
Understand how to style components in Next.js using CSS and fonts. ### 2. Add Fonts
NOTE: this codelab is not connected to the internet. Next.js downloads fonts from Google fonts with each build, so you might get some errors in the console, and might not be able to see the UI changes, even though the fonts are cached.
Next.js provides a way to customize your fonts by importing them from Google Fonts. This avoids the need to manually download and host the fonts yourself, or using a CDN to load them. Next.js will automatically optimize the font loading for performance, and serve them statically from your application server, reducing the number of network requests and improving the user experience.
Let's add a custom font to the application by importing the Open Sans font from Google Fonts. Open the
app/layout.tsx
file and import the Open Sans font:import { Open_Sans } from "next/font/google"; const openSans = Open_Sans({ subsets: ["latin"], });
This code imports the Open Sans font from Google Fonts and assigns it to the
openSans
variable. We can then use theopenSans
variable to apply the font to the HTML element in the layout. Update thehtml
element in theapp/layout.tsx
file to use the Open Sans font, by using theclassName
attribute:<html lang="en" className={openSans.className}>
-
Challenge
9: Server-side vs. Client-side Rendering
Objective
Understand the difference between server-side rendering and client-side rendering in Next.js. ### 2. State Management with Cookies and Server Actions
In a more complex application, you would typically use a state management library like Redux, React Context or Zustand to manage the state of the application, and perhaps combine it with server-side session management. For this codelab we will use a simple cookie-based approach to manage the state of the shopping cart. It's worth noting that this is not a secure approach, as cookies can be manipulated by the client, but it's good enough for our purposes.
Next.js provides a
cookies
object that allows you to read and write cookies in the browser. You can use this object to store the state of the shopping cart in a cookie, which will persist the number of tickets in the cart in the client's browser using a key-value pair.Next.js provides server actions, which are functions that run on the server side and can be called from the client side. This allows you to perform server-side actions like adding items to the shopping cart, and update the state of the application without reloading the page. Server actions are defined by creating a file that starts with the string
'use server'
at the top, and exports async functions that can be called from the client side, for example on a button component.Let's create a server action to add tickets to the basket by creating the file
app/events/[id]/actions.ts
with the following code:"use server"; import { cookies } from "next/headers"; export async function addToBasket(numberOfTickets: number) { cookies().set({ name: "ticket-count", value: numberOfTickets.toString(), }); }
This code defines a server action
addToBasket
that takes the number of tickets to add to the basket as a parameter. The function uses thecookies
object from thenext/headers
module to set a cookie with the number of tickets in the basket. This cookie will be used to store the state of the shopping cart.Let's update the
PlaceOrder
component to call theaddToBasket
server action when the user clicks the "Place Order" button. Open theapp/events/[id]/PlaceOrder.tsx
file and update thePlaceOrder
component to call theaddToBasket
server action:import { addToBasket } from "./actions"; // ... const handleClick = () => { addToBasket(currentValue); alert(`Order placed for ${currentValue} tickets for event ${eventId}`); };
This code imports the
addToBasket
server action from theactions.ts
file and calls it when the user clicks the "Place Order" button. TheaddToBasket
server action sets a cookie with the number of tickets in the basket, and the alert shows the number of tickets ordered.So now, let's update the
Navbar
component to show the number of tickets in the basket. Open theapp/Navbar.tsx
file and update theNavbar
component to read the number of tickets from the cookie and display it in the header:// ... import { cookies } from "next/headers"; export default function Navbar() { const cookieStore = cookies(); const ticketCount = cookieStore.get("ticket-count")?.value || 0; return ( // ... <p> <span>{ticketCount}</span> tickets </p> // ... ); }
This code imports the
cookies
object from thenext/headers
module and uses it to read the number of tickets from the cookie. TheticketCount
variable is used to store the number of tickets in the basket, and is displayed in the header. TheticketCount
variable is set to 0 if the cookie does not exist. Note that this is a server-side component, but cookies are stored on the client's browser. This works because the server sends a request to the client to update the cookies, and the client then includes the cookies in each request to the server, so the server can retrieve the cookies from the request headers and include them in the rendered component. Next.js handles all this automatically for us.NOTE: If you are having issues with the terminal disconnecting in the codelab environment, remove the line that creates the
alert
in thePlaceOrder
file.If you now navigate to the
/events/1
route and place an order for tickets, you should see an alert with the number of tickets ordered, and the number of tickets should be stored in a cookie in the browser. You should see that the basket in the navbar is updated with the number of tickets ordered.Of course, this is a very simple and incomplete example, as we're not storing the ticket IDs, and the customers can't remove tickets from the basket, but it should give you an idea of how you can use server actions and cookies to manage the state of the application in Next.js.
-
Challenge
10: Fetch Data and Handle API Calls
Objective
Understand how to fetch data and handle API calls in Next.js.
2. Fetch Data in the EventDetails Page
In a traditional Client Side application with React, you would typically fetch data from an API using
fetch
or a library like Axios, maybe using auseEffect
hook to fetch the data when the component mounts. This works, but makes the page slower to load, as the data is fetched after the initial render, and leaves the client in charge of fetching the data. Usually, a server will be able to fetch the data faster, and can pre-render the page with the data, improving performance.In Next.js, you can fetch data in the server-side rendering process using the
fetch
function and by defining anasync
function in the page component. This function will be called on the server to fetch the data before rendering the page, and the data will be passed as props to the component. This allows you to pre-render the page with the data, improving performance and SEO. It's important to note that this will be done by the server at build time, so if the data changes after the page is built, the client will still get the same HTML with the old data. We will see how to solve this later, but now let's see this in action by fetching the event details in the EventDetails page.Open the
app/events/[id]/page.tsx
file and update theEventDetailsPage
component to fetch the event details using thefetch
function. We define a new async functionfindEvent
that fetches the event details by ID from the API route, and then call this function in theEventDetailsPage
component, instead of thefindEventById
function:// ... async function findEvent(eventId: string): Promise<Event | undefined> { const response = await fetch( `http://localhost:3000/api/events/${eventId}`, ) if (!response.ok) { return undefined } const event = await response.json() event.date = new Date(event.date) return event } export default async function EventDetailsPage({ params }: Params) { const event: Event | undefined = await findEvent(params.id); // ...
This code defines a new
findEvent
function that fetches the event details by ID from the API route using thefetch
function. If the response is not ok, it returnsundefined
. Otherwise, it parses the JSON response and converts the date string to aDate
object. ThefindEvent
function is an async function that returns aPromise
of the event details. We then call thefindEvent
function in theEventDetailsPage
component to fetch the event details by ID.If you now navigate to the
/events/1
route, you should see the event details fetched from the API route and displayed on the page. You can also try navigating to a non-existent event ID like/events/abc
to see how the page handles the error.However, if you see the logs in the terminal, you will see that when you refresh the page, a call to
GET /events/1
is logged for every request, but a call toGET/api/events/1
is only logged once. This is because the API route is only called once, when the page is built, and the data is fetched and pre-rendered. This is a good thing for performance, but it means that the data is not updated if it changes after the page is built. This is totally fine if your page displays static content, like a blog, where content will not change after the page is built, but it is not great if you need to display dynamic content. We will see how to solve this in the next step. ### 3. Dynamic Data FetchingTo make sure that the data is always up-to-date, you can use dynamic data fetching in Next.js. This still fetches the data on the server side, but it will make sure that the page is revalidated and the data is refetched each time. To achieve this, we only need to make a small change to the
fetch
function in thefindEvent
function, by adding thecache: "no-store"
option:const response = await fetch(`http://localhost:3000/api/events/${eventId}`, { cache: "no-store", });
If you now refresh the page, you will see that the data is refetched each time, and the logs in the terminal will show a new call to
GET /api/events/1
for each request. This is useful for pages that need to display real-time data, like a dashboard or a chat application, where you want to make sure that the data is always up-to-date. ### 4. Incremental Static RegenerationThere's a third option for fetching data in Next.js, called Incremental Static Regeneration (ISR). This is a hybrid approach that combines the benefits of server-side static rendering and dynamic data fetching. With ISR, Next.js will pre-render the page with the data at build time, but will also revalidate the data and refetch it at a specified interval. This allows you to have the best of both worlds: fast page load times with pre-rendered data, and up-to-date data with revalidation.
To use ISR, you can define a
revalidate
option in thefetch
function that specifies the number of seconds to wait before revalidating the data. For example, to revalidate the data every 10 seconds, you can add therevalidate
option to thefetch
function:const response = await fetch(`http://localhost:3000/api/events/${eventId}`, { next: { revalidate: 10 }, });
If you now refresh the page, you will see that the data is refetched only if your request is more than 10 seconds later than the first one, and the logs in the terminal will show a new call to
GET /api/events/1
every 10 seconds. This is useful for pages that need to display real-time data, but don't need to fetch the data on every request, like a news feed or a weather forecast. -
Challenge
Conclusion
Conclusion
In this codelab, you learned how to build a simple ticketing application with Next.js. You learned how to create pages, components, and layouts, and how to use the Link and Image components. You also learned how to style components with CSS and fonts, and how to manage the state of the application with cookies and server actions. Finally, you learned how to fetch data and handle API calls in Next.js, and how to use server-side rendering, client-side rendering, and incremental static regeneration to improve performance and user experience.
You now understand the foundations of building a web application with Next.js, and you can use this knowledge to build more complex applications with Next.js. You can now explore more courses and codelabs in Pluralsight to learn more about advanced topics in Next.js.
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.