Loading video player...
How many times have you finally wrapped
your head around NextGS only for them to
drop another version that renames half
the features, breaks the other half, and
promises to simplify everything? Yeah,
same every single time. But this time,
it's actually worth it. Or is it really?
Hi there and welcome to the Nex.js crash
course where I'll teach you not just the
why and the what behind Nex.js, but also
dive straight into how to build real
projects with it. First, you'll go
through a deep crash course to visualize
and master essential Nex.js concepts
like architecture, rendering, routing,
caching, adapters, API, and its powerful
full stack capabilities.
Then you'll dive into the code, bringing
those ideas to life as you build a
realworld application designed to teach
you how to use these features the way
the top companies do. This crash course
alone will give you a rock solid
foundation. But if you're serious about
mastering NexJS and taking your skills
to a professional level, I've got
something special for you. The ultimate
Nex.js course. Right now, the most
popular Nex.js GS course on the internet
where we go beyond the basics and dive
deep into advanced enterprisegrade
features. And I just launched the
complete Nex.js testing course too. So
if you're watching this video right now,
you can still grab our launch week
bundle at 33% off. This is not a drill.
I repeat, this is not a drill. You're
getting two massive courses, the brand
new testing course and the ultimate
NexJS 16 course, plus a tactical testing
ebook, all bundled together. This deal
will disappear forever and the price
shoots back up. The link is in the
description, but honestly, pause the
video right now and go grab it because
the last time we did this, the early
birds cleaned it up in about 6 hours.
But if you're still wondering whether
NexJS is really worth learning, well,
just ask Netflix, Tik Tok, Twitch, Hulu,
Nike, Notion, or even OpenAI. The NextJS
hype is real, even though it changes
often. And if you don't dive in now, you
could be missing out on careerchanging
opportunities. So yeah, it's the perfect
time to impress employers and build your
portfolio with something that screams
production ready. Here's what you'll
build and learn inside this video.
file-based routing to master the core
structure that makes Nex.js lightning
fast. UI development where you're going
to design and develop beautiful
responsive interfaces that scale
effortlessly. Client and server
components where you'll finally
understand when, where, and why to use
each one. Database modeling with MongoDB
where you'll structure your data the
right way from the start. API routes
where you're going to build robust
backends without leaving your Nex.js GS
project also server actions image upload
and hosting with clouden for proper
asset management use cache the hot topic
of nextgs 16 SEO that works well
analytics powered by post hog an
open-source platform that gives you
product insights web analytics feature
flags AB testing and session replays all
in one place and all these by ensuring
you write production level code with the
help of code rabbit's intelligent
roasting code reviews and AI assisted
development using Warp where you'll be
the architect and AI will do things for
you perfectly mirroring real world
industry workflows. So once again,
everything you need is hiding in the
description like that bug you've been
looking for. Free video kit containing
the codebase, Figma designs, the NexJS
guide, plus the testing course bundle
that's 33% off until it self-destructs
in a couple of days. Seriously, check it
out. I know you never read descriptions,
but this time it's worth it. Time to
cook. Let's go.
Let me start with a very important first
question. What does NexJS have that
React doesn't? In simple terms, Nex.js
simplifies the development process and
optimizes your applications. It does
that through its primary
functionalities. Some of which you have
to actively decide to use and others
that you get out of the box just by
switching to Nex.js. The first of
Nex.js's benefits is its architecture.
Originally, React had two types of
components, functional and class-based
components. But now, components are also
categorized by where they run. If a
component runs in the user's browser,
it's called a client component. And if
it runs on the server, it's called a
server component. This division was
released in React 19. And although
frameworks like Remix and Astro also
support it, Nex.js was the first to
adopt it and fully utilize it. Since
server components are more performant,
Nex.js automatically converts every new
component you create into a server
component, unless you specifically
instruct it not to, only if you need
some browser functionalities, but more
on that later. The second Nex.js benefit
is in the way it handles rendering.
While React 19 supports server
components, Nex.js CS extends them to
more advanced rendering strategies,
allowing you to choose exactly where and
when they'll be rendered, optimizing the
performance even further. First is
client side rendering, which happens on
your browser. The server sends a basic
HTML document and JavaScript code, which
the browser executes to render the
components and display the website. And
the second is server side rendering,
which involves rendering the web page on
the server before transmitting it to
your browser. When you visit a page, the
server processes and renders the
components on the server and sends back
fully rendered HTML and JavaScript code
to the client instantly displaying the
website. But why does this matter? Well,
on top of the fact that your website
will load in 300 milliseconds and have
page speed insights that look like this,
serverside rendering will significantly
improve your website's search engine
optimization. Client side rendering
makes it almost impossible for search
engines to crawl and index your websites
as they're completely empty. Nex.js
solves this issue by sending
pre-rendered code, giving Google plenty
of stuff to rank us on. The next big
Nex.js benefit is of course routing. In
React, you need to install an additional
React router package to create routes.
On the other hand, Nex.js doesn't
require installing a separate routing
package and uses a much more intuitive
approach to routing called filebased
routing. Each folder's name becomes a
routes path. For example, a folder named
about will create a new for/about route.
Super simple, right? And connecting
directly to the routing system. The next
best thing about Nex.js is its evolution
from a simple front-end library to a
full stack framework using the same
file-based routing system I just
described. You can also create
serverless functions to handle native
back-end API requests. This means you
can create an API endpoint by simply
creating a file called route.js JS in a
specific folder without needing to worry
about the server infrastructure. Nex.js
takes care of scaling automatically. And
there are many other scaling features
that Nex.js provides out of the box like
automatic code splitting. While code
splitting in React is possible, it
requires manual configuration,
especially as the application grows.
Nex.js automates code splitting by
default. Well, what does that mean? It
means that when a user navigates to a
different page, only the code for that
page is loaded, which significantly
speeds up page load times. Pretty handy,
right? But that was just one out of many
built-in performance optimizations that
Nex.js provides. Font optimization
reduces layout shift, improves loading
time by pre-loading font files, and
reduces reliance on external services
like Google Fonts. Image optimization
automatically compresses images, and
applies lazy loading, CDN support, and
more. Script optimization optimizes even
third party scripts. And all of these
great optimizations come right out of
the box with Nex.js. And it's not just
performance. Nex.js is the definition of
cutting edge. It supports the latest
features such as React compiler, MDX
file integration, managing most SEO
related tags, analytics that run without
blocking UI, fast refresh server
components, hot module replacement, and
more. Oh, I know that was a lot, right?
That's totally okay. Stay with me and
I'll teach you everything I know about
Nex.js in this crash course.
Let's dive right into the demo and start
using the React framework for the web
used by some of the world's largest
companies, enabling you to create
highquality web applications. You can
either click get started or just copy
this installation command. Now,
throughout the demo, you'll see me use
WebStorm,
a super powerful JavaScript and
TypeScript IDE that as of recently
became completely free for
non-commercial use, which means that us
regular developers can finally get the
benefits of having a powerful IDE and
not just use a regular text editor. So,
feel free to download it. The link is
down in the description. And another
thing that makes working with WebStorm a
breeze is Juny, a smart coding agent.
You can also download it as you'll see
me use it across this project and it
just improves your productivity so much
more. Like you can ask it questions or
you can ask it to do things for you.
Throughout the course, I'll show you
more of what it can do. So once that is
done, we're ready to start from the bare
basics by creating a new empty project.
Webtorm allows you to immediately start
with a Nex.js generator which will
install the latest version of Nex.js for
you. In this case, just so we can all
follow along together, I'll start with
creating a new project and I'll call it
Nex.js demo. And we'll do it using
TypeScript and click create.
Immediately, we'll be right within this
project. So, open up your terminal and
paste the command you copied over from
Nex.js. That is mpxcreate-
next- app and then add the dot slash to
generate it in the current folder. But
for now, I'll just delete this source
folder and the additional files that got
created for me by WebStorm, just so we
have a completely clean slate. Once you
press enter, you might need to install
the installer. But once you do, you'll
be asked a question. Would you like to
use the recommended Nex.js defaults.
See, the installation process for the
new version of Nex.js got streamlined.
You can still customize your settings.
Like if you choose this option, you'll
be given like 10 more questions to
choose exactly how you want to architect
your project. But in this case, the
recommended settings are exactly what we
need. TypeScript, eslint, tailwind CSS,
app router, and turboac. So go ahead and
select the first option. And that's it.
Nex.js just started installing the
dependencies. And there are just a few
of them. React and react DOM and Nex.js.
That's it. When it comes to dev
dependencies, there's also TypeScript,
all the different types, as well as
Tailwind CSS and ESLint. And in a couple
of seconds, your project code is right
here. Before we go ahead and run it,
there's a new feature in Nex.js16 called
Turopac file system caching, which
allows for significantly faster compile
times across restarts. All internal
Versell apps are already using this
feature. So if you'd like to enable it
for your project, head over to next
config and then right at the top you can
say experimental and turn on the turboac
file system cache for dev and set it to
true. Theopac is an actual word. Looks
like webtorm doesn't know about it. So I
will save it to the dictionary.
Now let's talk about the project
structure. Your project is set up and
ready to go. You'll see many files and
folders. So let me walk you through them
from bottom to the top. Starting with
tsconfig.json.
This is the configuration file for
TypeScript. It defines what should be
type checked, ignored and which rules to
follow. After that we have a readme.md
which is a simple markdown file that
explains how to run the project and
provides all other relevant information.
Then there's the post CSS config m.js JS
which is a configuration file for post
CSS which is a tool used to process CSS
with different plugins. You can see here
that it mentions Tailwind CSS as a
plug-in which allows you to use its
utility first classes within your
project. Then there's the package lock
JSON. It's a file that locks the
versions of dependencies and their exact
subdependencies ensuring that everyone
working on the project uses the same
exact versions. Then there's the package
JSON which contains all the project
dependencies and scripts. You can see
that the name of the project is Nex.js
demo. Its version right now it's set to
private. Then we have the scripts such
as mpm rundev which will run the
application in development mode build to
run an optimized production build. And
there's also start and the lint package.
JSON also keeps track of other
dependencies and dev dependencies which
are only ran when we're developing.
After that we have the next env.
This file should not be touched. It's
simply used as a next.js TypeScript
configuration. And then a super
important file is next config.ts
which allows you to configure your NexJS
features such as experimental options.
You can see that there are many that you
can actually use here. Image settings,
build settings, and more. There's the
ESLint config which allows you to
configure ESLint for linting.getit not
get ignore, which allows you to ignore
some files not to be pushed over to
GitHub. And then we have two folders.
The public folder, which is a place for
static assets. You always need to put
your images and other static data right
here. There's also a node modules folder
here, but I've hidden it because I never
actually have to go into it. It's the
heaviest object in the universe, you
know. Well, jokes aside, it's a folder
containing all the dependencies and
packages that are required for your
application to run. And finally, there's
the app folder, which we'll explore
next.
You'll primarily work within this folder
on most feature implementations,
including creating pages, layouts, API
routes, and more. So, before we dive
into the details of the files within
here, let's actually run our application
by opening up our terminal and running
mpm rundev.
This will spin it up on localhost 3000.
And if you open it up, it'll look
something like this. To get started,
edit the page.tsx file. Okay. Okay.
Let's find where that page.tsx is. Looks
like it is here. This file represents
the homepage or the forward/ route of
your page. It contains some boilerplate
code by default. So, let's remove it
entirely. run rafce which is a super
nice snippet that I often use to create
a simple react a functional export
component.
So press enter and we can call it home.
If this snippet didn't work for you, you
must head over to plugins or packages
and then search for react snippets.
Any package should do. In this case, I'm
using modern react snippets. Here we can
say something along the lines of welcome
to Nex.js. GS. No longer in modern
versions of Nex.js is it needed to
import React. It is done by default. So
the view is even simpler. Let's explore
other files right here. There's the
favicon.ico
at the top. Any file with this name in
the root of the app will become your
website's favicon. It's just a naming
convention that allows you to add that
little image that shows next to your
title within the browser tab. There's
the globals.css CSS which is where you
can write all your custom CSS or
Tailwind CSS utilities. Then there's the
layout.tsx.
This is the main entry point for your
application. So anything that you do
here will be applied across all pages
and routes. That's why here we're
importing and setting different fonts as
well as the application metadata. For
example, if you change the metadata from
create next app to welcome to Nex.js,
you can also change the description to
something like check out jsmastery.com
for more. Oh, and this is definitely not
true. JS Mastery is a real word. So,
I'll add it to the dictionary. That's
much better. So, now if you head back to
the browser, you'll see that the title
changed. And you can also see welcome to
Nex.js. If you go ahead and change some
class names, like making the text extra
large, like saying 5XL, or making it
underline, as soon as you get back to
the browser, you'll be able to see
changes instantly thanks to Nex.js's hot
module replacement. So, now that you're
familiar with the entire Nex.js
application structure, in our next
lesson, we'll get our hands dirty.
At the start of the crash course, we
talked about architecture where we can
write two types of components, client
and server components. If you remember,
I said that by default, the React
components you'll write are server
components. But are they really? If I
head over to page.tsx, where we have our
homepage component, let's add a console
log before running this return. I'll say
console.log log and I'll say what type
of a component am I? Sounds like it has
an existential crisis right here. But
don't we all when new versions of Nex.js
drop. Now tell me where do you think
this console log will show up in the
browser? I was about to say no, it won't
appear in the browser because it's a
server component, but in recent versions
of Nex.js, JS they actually extended
that server console log so that it
appears in your browser's console as
well. But don't let this confuse you.
This console log is not happening within
the browser. Nex.js is just sharing it
here with us for convenience. Server
components are rendered on the server.
So this brings me to the topic of React
server components. They're the
components that are rendered on the
server and their HTML output is then
sent to the client. Since they're
rendered in the server, they can access
serverside resources directly like
databases or the file system. This helps
reduce the amount of JavaScript sent to
the client, improving performance.
Server components are excellent when you
need direct access to serverside
resources like accessing files in a file
system or you want to keep sensitive
information well sensitive such as
access tokens and other keys safe on the
server. All right, but if server
components really are better, why can't
everything be a server component?
Well, if your component requires browser
interactivity such as clicking buttons,
navigating to different pages, and
submitting forms, then you need to turn
it into a client component. So, what are
React client components? Client
components are of course rendered on the
client side. And in this case, client
side simply means the browser. To use
them in Nex.js, you must add a use
client directive at the top of the
component. So, back within our demo,
let's create a new component. We can do
it within a new folder called
components. And then within the
components folder, create a new file
called hello.tsx.
Within here, I'll create a new React
functional component like this. And it
doesn't differ from the previous server
rendered one. All until one point when
we give it a use client directive. This
line right here will make sure that this
entire component is client side
rendered. So if we give it a console log
and say something like I am a client
component and now we import it into our
homepage by heading over to page.tsx
and we render it right here below this
div. But first, I'll wrap both of them
within a main tag. And then I will
render the self-closing hello component
which we're importing from app
components hello. Now back within the
browser, we still see this server log
which is fine. And of course, there is
the IMA client component console log,
which makes sense because we're
rendering this client component well on
the client, the browser. But to verify,
let's quickly check out the terminal.
Oh, and what is this? The log of the
client component is also here. But how?
Or more importantly, why? It's making me
question myself now. Well, there is an
explanation. Well, this is because
server components are rendered only on
the server side. While client components
are pre-rendered on the server side to
create a static shell and then hydrated
on the client side. This means that
everything within the client component
that doesn't require interactivity or
isn't dependent on the browser is still
rendered on the server. The code or
parts that rely on the browser or
require interactivity are left as
placeholders during serverside
pre-rendering. When they reach the
client, the browser then renders the
client components and fills in the
placeholders left by the server. I hope
that makes sense. And that answers the
question of what is serverside
pre-rendering, a pretty cool feature by
Nex.js. I hope that's clear now. But if
you're still unsure, take a second to
pause it and rewatch it until it clicks.
You can also add some additional cons
logs or components and then render them
within the page.x.
Don't proceed further if that doesn't
make sense. And for a deeper dive with
clearer explanations and more visuals, I
would recommend checking out the
ultimate Nex.js course where I break
down the entire client server
architecture in detail. So finally, when
should you allow Nex.js to turn your
components into serverside components
and when should you manually change them
to client side? Well, a good rule of
thumb is to leave it as serverside
component until you need some browser
interactivity, at which point you'll
most likely get an error and then you
can add the use client directive at the
top. Now that you understand the React
components and how they work in Nex.js,
there's an important update in Nex 16.
React compiler support is now builtin
and stable. It automatically memorizes
components to reduce unnecessary
rerenders. No more of those use memo and
use callback hooks. The compiler
analyzes your code at build time and
optimizes rendering behavior
automatically. To enable it, we have to
install the plugin. So, open up your
terminal and I'll actually keep this one
for my application running and then I'll
open up another one which I'll call
terminal within which you can run mpm
install babel plug-in react compiler at
latest. Once it installs, we can head
back over to our next config.ts.
And at the top, we can set the React
compiler to true. That's it.
Now, it's time to explore routing in
detail because doing everything in a
single page is boring, right? Like right
now, we only have a homepage, but what
about adding an additional page like
forward/about?
Well, Nex.js GS uses filebased routing
which means that all the pages and
layouts can be right here within the app
folder. Oh, and therefore it's important
to mention the components folder
contains components and not pages which
means that it needs to be outside of the
app folder. Then within the app, create
a new folder and call it about. Within
about you can create a new page called
page.tsx.
And in there, you can render a simple
component
that simply renders an H1 that says
about. Now, if you head back over to
your application and modify your URL to
for/about,
you can see that you'll instantly be
redirected to the about route. It's
being displayed in the UI. That's how
you create routes in Nex.js. As opposed
to a very boilerplate way to do it in
React using React Router, in Nex.js, we
use a filebased routing system where
folders are used to define routes. The
folder name becomes the route name and
the page.tsx renders the UI for that
route. It's like a special file for
displaying the routes content. But
that's not enough. Let's say you're
developing an admin dashboard within
this application and you want to create
a route something like slashdashboard
slash users and forward slashboard
slan analytics. These are two different
pages. Of course, you can't create two
folders with the same name, right? This
is where we use nested routes. So, let
me show you how we would do this. Simply
create a new folder called dashboard.
Inside of which we'll create two more
folders, one named users and the other
named analytics.
Both of these folders will contain their
special page.tsx
within which I'll simply runce.
I'll rename this one to analytics. And
then within users, I'll also create a
page.tsx.
And I'll call this one users.
Now, if you visit both of these routes
by heading over to dashboard and then
users, you'll be able to see users. And
if you head over to dashboard/analytics,
you'll be able to see analytics.
Perfect. That's exactly what we wanted.
Still, this may not be enough because
what if you want to display each user's
profile in the dashboard? Let's say that
we have four predefined users in
dashboard users. So right here, I'll
call it dashboard
users that can be within an H1. And then
right below it, we'll have a ul an
unordered list with a class name of
margin top of 10. and then within it
I'll create an LI with users from one to
four like this. Now let's say that you
want to make each one of these list
items clickable so that when you click
on user one you want to show full user
details on a new page like dashboard
users user one and so on.
You can't create these folders manually
right here within users like user 1,
user 2, user 3. I mean, you could, but
you would not be going anywhere because
the next day there's going to come a
user 5, 6, or 10,000 and your life can't
simply turn into creating folders. I
think you have a bigger purpose than
that. So, this is what dynamic routing
is all about. It's a route where part of
the URL can change or be dynamic,
typically based on the user input or
specific data. And creating a dynamic
route in Nex.js is simple. All you have
to do is wrap the changing part in
square brackets.
In our case, it'll be forward/dashboard/
users. And within users, I'll create
another folder that'll start and end
with square brackets. and within it I'll
say ID.
Then within this weird named folder I'll
add a new page.tsx.
Now we'll modify the general users page
to make each one of these allies a link.
I'll do that by selecting multiple
cursors and render a link component
coming from next link. So make sure to
import it at the top. Then to each one
of these links, I'll give an href of
forward slashdashboard
slash users slash and then their number
like one 2 3 and four. We can now close
this link and then add the user text
within it. That'll look something like
this. Now, before we test these links
out, if you now head over into this ID
page and run rafce and call this user
details
and then you head back over to your
forward/dashboard/
users page, if you try to click on some
of these links, you'll automatically be
redirected to the user details page. 1 2
3 4.
But each one of these just renders the
text of user details. It's nothing
specific. So how should we know which
user's details should we actually
render? Well, exactly for that purpose,
there's a concept of params. See this
part within square brackets, the ID,
whatever it says right here gets
populated within params. So the
structure the params and we can define
their type. params are actually a
promise which gets resolved
in an id which will be of a type string
which means that we can now extract the
ID from params but since it's a promise
we have to await it which means that we
need to turn this entire function into
an asynchronous function. Yep, that's
valid. These are called page params and
you can get these through the next.js GS
page only. If you want to get this info
somewhere else, you either have to pass
it down through a component or use a
hook called use params. So what you can
do now is modify the text right here to
render an H1 and say showing details
for
user number and then we can render the
ID because the ID is actually either 1,
two, three or four. So if you head back
and now switch over to a couple of
users, you can see that each one of
these page will be a new dynamic user.
Right now we're just rendering their
number, but I think you can imagine that
later on we can show entirely different
profile pages, user details, and maybe
the posts they generated on this
platform. So now you know how the
file-based routing in Nex.js works, how
we can do nested routes as well as
dynamic routes through page params.
Great work. Next, let's talk about
layouts. Earlier, I briefly went through
the main layout file. This one right
here in the app. And I call this the
starting point of our application. To
demonstrate that in the concept of
routing, if you go back to that file and
then right here at the top of the body,
you add some piece of text like coming
from the main layout.
And if you go back to your main route,
which is the homepage,
you'll see coming from the main layout,
then the text right here, and then
hello. But if you switch over to
localhost 3000/about,
you'll also see the text that says
coming from the main layout. And this
will happen for every other page,
including dashboard analytics or
dashboard users. Yep, it's here, too.
This is because layout tsx acts as the
parent for all of these routes.
It allows you to share UI elements like
this piece of text right here across
multiple routes. For example, you can
place features like a navbar or a footer
right here. Let me give you an example.
I'll say navbar right here. And then
below the children, I'll render the
footer.
So now if you go back you can see that
it appears across all of these different
pages on the homepage on the about page
and even on the analytics users page.
Simply add them once within the layout
and they'll be available whenever
needed. In Nex.js a root layout like
this one is always required but you can
create additional layouts if necessary.
Let's say you want to render a specific
UI just for the dashboard routes. these
two right here. You can do that by
creating another layout inside of the
dashboard folder. I'll call it the same
layout.tsx.
Don't try to call it something else
because otherwise Nex.js won't recognize
it. Now, within it, you can create
another React functional component
because a layout technically isn't
anything else than just a component, but
it works on the principle of using props
of children. children is nothing else
than a prop that is of a type react
react node. So basically it's another
component that we can pass within this
layout for it to get rendered. So if you
wanted every single component in the
dashboard to have like a special
dashboard navbar, you could create a p
tag here that says dashboard navbar and
then render the rest of the children
which is basically the full pages that
you're trying to render. If you now
visit the users dashboard, you'll see
both the regular navbar coming from the
original layout as well as the dashboard
navbar coming from the second layout.
Hopefully that makes sense, right? But
wait, what if you don't want the root
layout to appear in the dashboard routes
and only be shown in non-dashboard
routes like the homepage? This means
that you need a separate navbar for the
dashboard and another one for the
non-dashboard routes which is very
similar to how things work in real life.
Right? In such scenarios, you can use
route groups. They allow you to organize
your route segments and project
structure without impacting the URL
path. This means you can create folders,
but unlike nested routes, these won't
show up in the URL.
How to do that is by simply wrapping the
folder name inside parenthesis. In our
case, let's create two folders with two
names. The first one will be right here
within the app and I'll call it
dashboard, but this time wrapped in
parenthesis.
It'll be for all the dashboard related
routes. And then I'll create another
folder within the app and I'll call it
root also in parenthesis for other
non-dashboard routes and move the
page.tsx as well as the about folder
within the root folder.
Now remove this navbar and footer from
the original layout. This is the primary
layout. And instead create a new layout
within the root directory.
It'll be called layout.tsx.
You can run rafce. As you know, you're
going to get access to the children. So,
we can even duplicate the layout that we
have within the dashboard
and simply paste it right here. This
time I'll give it just the navbar.
So now we have the root within it. You
have some folder of about and then you
have another layout and page. also move
this layout from the dashboard without
parenthesis into the outside dashboard
folder. So now both of these route
groups so-called have their own layouts.
So with all of those changes, let's
check how everything is working. Now if
you head over to the homepage, you'll
see that here we have the general
navbar. If you head over to about,
there's also the general layout navbar.
But now if you head over to localhost
3000 slashdashboard
slashanalytics for example
here you'll see that we have a dashboard
navbar and this one is also shared
within dashboard users.
Perfect. So why are we using these route
groups in the first place? Well route
groups allow you to create folders
without affecting the URL. See, you
don't have to go to root about or root
page. This is not taken into account
when you go to the URL bar. Rather, it
is still just home or forward slash for
the home and about for the about. And
then you don't have to do the dashboard
dashboard analytics. Rather, it is just
dashboard analytics because this is not
a route group, but this one is. So, even
though creating route groups isn't taken
into account when forming URLs, it still
allows you to add layouts inside of
them. And keep in mind that there has to
be only one single primary homepage.
Since route groups aren't taken into
consideration when forming URLs, if you
create a new page.tsx within dashboard
here and you already have one within
root, that would not work because these
two files would fight on which one would
prevail. So let me give you an example
right now. If I create a new page tsx
within the dashboard and create a new
component within it, you'll get an error
saying that you cannot have two parallel
pages that resolve to the same path.
Check out dashboard and root. So I'll go
ahead and delete this one. And from
Nex.js 16 onward, routing and navigation
have been completely reworked, making
transitions much faster. The biggest
update is layout dduplication. When
pre-fetching multiple URLs that share
the same layout, the layout is
downloaded only once instead of per
link. This optimization will happen
automatically with no code changes
required. That's what I like to see.
Let's talk about error handling. In
Nex.js, there's a special file called
error.js JS or TS if you're using
TypeScript that catches errors and
displays them on the UI. Similar to how
we were able to create multiple layout
files for each folder, be that a route
group or a route folder, we can do the
same for the error.ts file, let's throw
an error on purpose within the about
page. So head over to root about
page.tsx.
And right at the top of the page, I'll
say throw new error. And let's say that
this feature is not yet implemented like
this. Now if you visit the about page,
you'll see what most developer don't
like to see. A huge error popup that
breaks our entire application. Instead
of this, what would have been much nicer
would be to see this error nicely
packaged within a nice UI. To fix this,
we can create a special air.tsx file in
the root folder. So let's do it right
here. And I'll call it error.tsx.
And apparently this error file has to be
a client component. So we have to add a
use client directive. Do you know why?
Well, it's because these errors happen
on the client or user side. So if we
want to show them there, it has to be a
client component. Now, if you check out
the error handling documentation, you'll
see an example of a simple client error.
Just search for error.tsx. I'll go ahead
and copy it and then paste it right
here. It'll look something like this. It
says air boundaries must be client
components. We import use effect for
side effects. We get the error and the
reset coming from props. We console lock
the error within the use effect.
Whenever the error changes and then we
display a message saying something went
wrong and then maybe a button that helps
us reset it. So, if you revisit the
about page now, even though there's
going to be a warning that you can open
up right here, you'll be able to see
something went wrong and then try again
buttons right on this page as well. So,
you can more easily explain to the user
what really happened. If you just want
to have one global error, then you can
do it by creating a global error.tsx
file in the root of the app folder.
Unlike layout.tsx, which displays
everything from its parent, the error
file works differently.
Only the closest error file to the route
takes priority, meaning you won't see
the content both from global error.tsx
and from the root error tsx. Errors will
bubble up to the nearest parent error
file, not all of them. Make sense? And
to talk about something else other than
errors, let's talk about loading UIs.
Loading UIs in Nex.js GS works very
similarly to error handling. You want to
show some kind of a loading progress
while data is being fetched for users
with a slow internet connection. It's as
simple as adding a new loader.tsx
file within the app. And within it, you
can add some kind of a loader or spinner
component.
While the data is loading, Nex.js will
show the content you specified in the
loading file. And once the data is
received, the content in the page you're
trying to render will be shown. It's
super useful and surprisingly easy to
do. I've covered this in more detail in
the Ultimate Nex.js course. As you can
see here, there's an entire section
dedicated to it, complete with
challenges to help you practice. But in
this crash course, you'll also see this
in action in the next segment where
we'll actually do some real world data
fetching. Nex.js XGS16 also introduces
forbidden and unauthorized special
files. They work similar to loading. You
can use them to show a nice UI asking
user to sign in or tell them that they
have no permission to view the page
instead of simply showing an error or
redirecting them. You can explore all of
these within the next.js docs or within
the ultimate next.js course if you want
to go deep.
Data fetching. The most interesting
concept in Nex.js.
There are different ways in which we can
fetch content. A traditional way is
using a use effect. For example, this is
what you would do. Let's say that you
have a homepage where you're trying to
set and fetch some albums. You create a
new empty use state. You declare a use
effect within which you declare an
asynchronous fetching function.
Within it, you have an await fetch where
you're trying to get some albums and
then you return that JSON and set it to
the state. Finally, going a bit down,
you map over those albums and show the
data. But this isn't super efficient.
There is a better alternative to fetch
and it's happening on the server side.
Not only is it faster and more
efficient, the code is also so much
cleaner. Check this out. You declare a
new functional component called home.
And immediately on top of it, you make a
fetch request. You check if it fails. If
it doesn't, you simply declare it and
set its response.json to the album's
variable. You map over it and display
the details. Go ahead, pause this video
and test it out. You don't necessarily
have to do all the styles, but you can
at least try to fetch the data, map over
the albums, and show each album's title.
If you do it, visit the website and see
if it works. You'll not see anything.
You'll have to refresh to see the
result. And it's not because HMR is not
working, but rather HMR is caching your
content. In latest versions of Nex.js,
JS there is a new feature called server
components HMR cache that allows you to
cache fetch responses in server
components across hot module replacement
refreshes in local development. What
this means is you'll have faster
responses and reduced costs for build
API calls. But now coming back to the
difference between server side fetching
versus client side fetching. First of
all, you'll notice the code difference
between the two strategies. With
serverside fetching, you wrote fewer
lines of code, which improves the DX
developer experience. But that's not the
only benefit. Serverside fetching has so
many more benefits like improved initial
load time as a serverside data fetching
allows the page to be rendered with the
data already included. It reduces the
time to first contentful paint FCP. In
the client side example, the user sees
an empty page until the data is fetched
and then rendered. The second and most
important benefit of server side
fetching is better SEO. Search engine
crawers can more easily index content
rendered on the server as the content is
already provided in HTML format.
In contrast, client side fetching may
result in content not being visible to
crawlers right away since it first needs
to be fetched and then displayed on the
UI, which can negatively impact your
SEO. Next, not only is the code shorter,
but the logic is simplified.
Server components allow you to keep data
fetching logic on the server closer to
your data source. This can simplify your
component logic and reduce the need for
use effect and use state hooks. And the
next benefit is not that easy to
understand at first and it is automatic
request dduplication. Nex.js provides
that automatic request dduplication when
fetching data on the server which can
improve performance and reduce
unnecessary API calls. Basically,
request dduplication makes sure that
when the same data is requested multiple
times at once, only one request is sent.
It stops duplicate requests from being
made. And when talking about benefits of
the server side fetching versus client
side fetching, I really can't miss on
improved security. Keeping API calls on
the server allows you to better protect
sensitive information like API keys
which should never be exposed on the
client side. And adding to the list of
benefits, we have reduced network
waterfall. Clientside data fetching
often leads to a network waterfall where
requests are made sequentially.
Serverside fetching can more efficiently
parallelize those requests. I'll teach
you how to do these parallel requests in
our build and deploy project. So, keep
watching. But this isn't just about
fetching. You can server render any
other calls. Maybe a direct database
call to Prisma to get the list of posts
or even with mongoose and MongoDB like
this. That's the beauty of Nex.js.
This is achievable because these
components are React server components
which allow you to access server related
resources directly. This means we can
make direct database calls instead of
needing to create an API and then fetch
that API once again. And if you're still
not convinced, you can always go for
client side fetching. How? Just add use
client to the top, bring back the use
effects, the use states, and you're all
set. Next.js will allow you to do
whatever you think is best. And let's be
honest, this is how we all used to use
Next.js when we first transitioned over
from React. It only feels natural, but
it is the wrong way to use Nex.js. And
that's what made me create the whole
ultimate Nex.js course in the first
place to teach Nex.js the right way. But
now take a moment to test both client
side and serverside rendering. You'll
notice the differences I mentioned such
as seeing the blank page initially with
client side rendering or if you inspect
its source and see that nothing is there
or client side not functioning at all if
you disable JavaScript. In contrast,
serverside fetching will still display
the results even if JavaScript is turned
off in the browser.
I have to tell you a bit about API
routes.
Enough with front-end stuff,
optimization caching performance and
all that. It's time for some backend
work. If you've ever done some backend,
even creating a simple hello world from
server message requires a fair bit of
setup. It involves setting up a project,
installing necessary packages like
Express for Node, writing server code,
setting up Express the port, and then
making that port listen, running it, and
then deploying it using some paid or
free service. so you can finally use it
on the front end.
It might not seem like a big deal when
you're just reading or looking at the
code, but things quickly start getting
complicated when you have to write
various routes, middlewares, and so on.
But in the newest version of Nex.js,
it's super simple, much closer to what
we did on the front end. You just create
a special file within a folder for the
specific route, and you're all set.
There's no need to set up, manage, or
monitor an active server separately. If
you want to show the same message hello
world from backend in nex.js, you simply
need to create a folder with any name
and then create a special file named
route.ds inside it. From there, you can
immediately begin writing server code.
Export async function get return
response.json
message hello world from back end.
That's all there is to it. Your folder
name serves as your API route name with
your business logic neatly encapsulated
within this special route file. If you
now go to the browser, modify the URL
and add hello world to it, you'll see
hello world from back end as a response.
But how can you create other API
endpoints like post, patch, delete, and
more? Well, for that, let's create a
book endpoint with a local array as a
database. A common practice when
creating route handlers is to create a
folder called API and write all the
routes inside it. So create this API
folder in the app directory and add a
database file for storing some dummy
books. Then you can create two routes
get and post in app API books route.tsx.
These are nested routes similar to what
you explored previously in the routing
part for the UI. For the delete and put,
you'll have to create dynamic route
handlers and you already know how to do
that. app API books and then dynamic
square brackets idward slout.ts.
And inside of it, you can export async
function put for the update request and
then export async function delete for
the delete request. And then you can add
the logic. So far this feels good.
Similar to what we did with UI routes,
but this time for writing server code
and creating APIs.
So let's test them one by one to see if
they work or not. And there you go.
Everything works perfectly. This is how
route handlers or APIs work in Nex.js.
It's super simple and straightforward.
And the creation of APIs in Nex.js GS
was nice, but how can you use them on
the UI? Well, it's a simple fetch. Just
create a books route real quick and call
the get API route to fetch the content
const response await fetch localhost
3000 API books. You get them from the
response, set them to the state, and
render them. If you visit the books
route, you'll see all of your books as
easy as it could be. Thanks to the
serverless architecture of Nex.js GS and
React 19 React server components.
And let's talk about caching. Caching
isn't new, but in Nex 16, it's been
completely reworked. So what is caching?
It simply means storing data
temporarily. So it can be reused instead
of refetched or rebuilt. In Nex.js,
caching happens across multiple layers.
There's a browser cache, which saves
static files locally. There's the server
cache, which stores pre-rendered pages
and API responses. And then there's the
data cache, which remembers fetched data
to avoid repeat requests. Together, they
make your app feel instant. So, let's
talk about the new caching model. See,
the old modes like the SSG, ISR, and PPR
still exist, but now they're just
outcomes of how you define cache
boundaries, not separate settings. You
no longer choose SSR or SSG. You simply
define what gets cached and Nex.js
handles the rest. You can enable this
new system by simply adding an
additional flag to your next config.
Caching is disabled by default. So you
must explicitly decide what to cache.
Like use client or use server. You can
mark routes, components, or even
functions with use cache. This tells
Nex.js to store and reuse the output if
inputs haven't changed. For example, you
can define it on a file level, component
level, as well as on a functional level.
Simply placing use cache at the top of a
layout or a page pre-renders it at build
time, stores it in memory, and then
revalidates it automatically every 15
minutes by default. If you want to
fine-tune how cache works, there's the
cache life method which controls for how
long data stays cached. You can write it
something like this. Use cache at the
top, import cache life, and then define
cache life. maybe hours which is going
to cache the data below it for 1 hour.
You can also define custom lifetimes in
your config and you can also use cache
tag to group cached items for easier
invalidation.
Cache life controls when to clear and
cache tag controls what to clear. To
refresh constant instantly you can just
use revalidate or revalidate tag. Now,
when talking about caching, we also have
to talk about partial pre-rendering or
PPR. See, with cache components option
enabled, you don't need PPR set to true.
Static parts marked with use cache are
pre-rendered automatically, while
dynamic parts stream in with React
Suspense. The takeaway here is that this
is a completely new caching mindset.
Same principles, different syntax. It
will take some time to adjust, but once
you do, it'll give you precise control
and better performance. Let's just hope
that we don't see use and next next.
Jokes aside, I already started working
on the new caching module within the
ultimate next.js course.
But now, let's talk about something even
newer, a feature that'll make deployment
and scaling way more flexible. After V16
of NexGS, we also have build adapters.
They're currently in alpha and it's a
big deal for everyone using Next. Until
now, the NextGS build process was
tightly connected to how Versel worked
behind the scenes. That made sense.
Versel created Next, but things are
changing. With a new build adapters API,
you and hosting providers can hook
directly into the build pipeline and
customize it. Think of an adapter as a
small bridge that teaches Nex.js how to
build for different environments.
Adapters can modify your Nex.js is
config before build time. Transform the
final build output or adjust how your
app runs depending on where it's hosted.
Be it Versel, AWS, Cloudflare, Netleifi,
or even your own server. This means zero
config deployments on any platform,
environment specific optimizations, and
most importantly, freedom because now
you're not tied to just one provider.
It's the start of a more open and
flexible Nex.js ecosystem. So, it's
definitely something to keep an eye on.
One final thing I want to teach you
before you're ready to start creating
your NextJS application, and that is
search engine optimization, specifically
how we use metadata in Next.js to
improve our SEO. And when I mention
metadata, I'm referring to the look and
feel of our website when we share it
with others on messaging platforms or
post it on social media platforms or on
internet in general like for example in
search engines. In today's world, we
need to ensure that our content is
sharable. So there are two ways in which
we can manage metadata in our next.js
application. The first one is config
based. All you have to do is create a
JavaScript object in the layout or any
kind of page file and export it. Nex.js
will automatically detect it and turn it
into relevant meta tags for those
routes. Something like export const
metadata is equal to and then in there
you can provide all sorts of different
SEO properties such as title,
description, thumbnail, and more.
because it's higher on the priority
list, it'll override the default
specified in the global layout. This
allows you to either define unique
metadata for each route or rely on the
metadata from the root layout. It's
entirely up to you. But everything we
discussed so far is just static metadata
means that it's not going to change.
Home is always going to be home. But
there are also going to be cases in
which you want to create dynamic
metadata based on some kind of content
on the page such as a blog title. It's
super simple. The only thing you have to
do is export a new async function called
generate metadata which gets access to
params. Params being the same params
that we had before from dynamic routes.
You can extract the ID or the name or
anything else. Based on that ID, you can
get full resource details or user
profile details, whatever it is. Then
you can use those details to formulate
SEO title, descriptions, thumbnails, and
more. And you can simply return a
dynamic object of all of these
properties. That's it. As simple as
that. Are you with me so far? Good. And
the other way in which we can set
metadata in Nex.js is filebased
metadata. As the name suggests, you can
put files like a robot, sitemap,
favicon, open graph images or other site
icons directly inside the app folder and
Nex.js will automatically detect and
generate corresponding meta tags. For
example, you can have an app and then a
favicon and then an icon and then an
open graph image, Twitter image. All of
these properties will get added to your
meta tags. It's just about adding the
files with the right names. They have to
be proper names directly inside the app
folder. It'll work out the same as the
config based approach. You can find the
full list of all files that can be
created to define metadata here. But for
the time being, I still prefer creating
it by exporting the config file from
layouts or individual pages. And it's
worth noting that file-based metadata
has a higher priority and will override
any config based metadata. So if you set
it in a file, it'll be used instead of
the ones that you set in the
configuration. So now you know how to
make your apps sharable, SEO optimized,
and search engine crawable. With that in
mind, I think you're ready. First, there
was a lot of theory, and then there was
more theory disguised in this practical
part, but now you're ready to dive into
code. You're ready to dive right in and
build and deploy your Nex.js GS app with
all of these optimizations, performance
updates, and new features baked right
into it.
And now comes the more exciting and
hands-on part of this crash course.
While understanding the theory is
essential, knowing how to apply it is
what truly sets you apart. So, let's put
everything you've learned into action by
building and deploying dev events.
At first, it might seem like a boring
application, just two pages where users
can browse upcoming events, view
details, and book a spot by entering
their email, like a newsletter signup.
But I've chosen this idea for a reason.
Behind that clean, modern UI lies a full
stack learning experience. See, by
building this app, you'll get practical
exposure to file-based routing, React
server components, client versus server
components to understand when and why to
use each database modeling where you can
design and structure backend data layer
API routes that you can build and
connect your front end to them, server
actions to perform server side mutations
securely. You'll also apply the new use
cache caching model for instant updates
and then we'll also track the analytics
of our platform to understand how our
users interact with it. This project
will walk you through design,
development and deployment all the core
stages that bring any application to
life. So let's dive right in
to start developing your app with the
framework used by some of the world's
largest companies. We can start with a
single command mpx create next app at
latest. But first we have to set up our
coding environment so that we can
actually paste that command somewhere.
I'll be using webtorm as my IDE as of
recently it became completely free for
non-commercial use. I'll leave the link
down in the description so you can
download it and try it out too. Once you
open it up, you'll be able to quickly
create a new project. I'll start with an
empty project and name it Nex.js crash
course. Create it and open it up. Then
you can open up the built-in integrated
terminal and paste the command you just
copied. But make sure to add a dot at
the end which will make sure that the
application gets installed within your
current folder. And then it'll ask you
whether you would like to use the
recommended Nex.js defaults such as
TypeScript ESLint Tailwind CSS App
Router, and Turboac to which we'll say
yes, this is the exact text stack that
we're using. Then it'll proceed with
installing the necessary dependencies
which there are very few of such as
React, Reactdom, and Next, and then also
dev dependencies from TypeScript and the
corresponding types over to Tailwind CSS
and ESLint. Once it installs, you can
run mpm rundev to run it on your local
host 3000 and then open it up. We get
this new and updated boilerplate page
which looks even simpler than before.
And it says looking to get started just
edit the page.tsx file. So let's explore
the app structure. You can see that not
a lot has changed. We still have this
page which contains the bulk of the
content. So let's go ahead and remove
all of it and just run rafce to quickly
spin up a new react air function
component with exports. React imports
are no longer necessary. So we can just
say welcome to Nex.js 16. If this
command didn't work for you, you can
head over to your plugins or packages
and search for React snippets. In this
case, I'm using modern React snippets.
If you made the changes, you should be
able to see that now it says just
welcome to Nex.js16 on your local host
3000. Now throughout this course, I'll
teach you how to build a dev event
next.js application. I found this to be
the perfect project to go over all the
primary but also the latest Nex.js
functionalities. So together we'll
explore what Nex.js GS has to offer
while building a couple of these
beautifully designed and fully mobile
responsive pages. Of course, the focus
of this course is the main star of the
show, which is Nex.js. So, we don't need
to focus too much on the CSS. For that
reason, in the description down below,
you'll be able to find a complete media
kit containing the link to the Figma
design, which you can explore and
rebuild on your own if you want to, a
complete source code, as well as some
snippets that are going to make the
building of our application easier,
allowing us to copy very lengthy and
redundant uh presentational stuff and
allow us to focus only on what matters.
So, let's start with copying this
global.css CSS file and then heading
over within our application
app globals.css.
Select everything and overwrite it with
what we copied over from the globals.css
within the snippets down below. What
we're doing here is just setting up the
base theme of our application with some
styles that we'll be using later on.
modifying the color of the body as well
as adding some helper utility functions
that are going to help us style
different headings, navigation items,
and more. And then some components for
the hero sections, events, cards, and so
on. Now, you can see that this CSS file
also requires us to install TWW,
Tailwind Animate CSS. So, go ahead and
install it, too. It is a dev dependency
that some styles require. So open up the
terminal. In this case, this terminal is
already running the application. But
thankfully, WebStorm allows us to open
up multiple terminals. So I'll say app
and then I'll open up another one which
I'll call just terminal. Within here,
you can run mpm install-save-devtwan
animate CSS. And as soon as it gets
installed, you should be able to see
that this red squiggly line will be
completely gone. There we go. Here it
is. Now, alongside all the styles,
there's also a couple of different icons
and images that we'll need for this
application to look this good. Starting
from this little logo icon to maybe some
images that we can use for the events or
maybe we have some icons that we want to
use here. You can see that we're using
this very cool icon style. So, I took
some time to find them and I'll give
them to you right here within the assets
folder.
So, in the video kit, click on the
assets,
download them. Don't worry, there's no
viruses on there. Once you download
them, you can unzip them. And here
you'll see a public folder. So, what you
can do is delete the current public
folder.
And then simply drag and drop this new
one in, as well as drag and drop this
new favicon that we have for our
application into the app folder to
replace the current one. Now, if you
open up the app, it'll still be more or
less the same as what we had before, but
let's actually put it side by side by
our IDE, so we can see the changes we're
implementing live as we code them. So,
I'll drag and drop it right here to the
side and give it some space. Now, you
can see under the tab name that the logo
changed or the favicon, but the title is
still create next app. So, let's go
ahead and modify that by heading over
into app layout.tsx. tsx where we're
importing different fonts and here's the
app metadata. So what we can do is first
change the metadata to the real app name
such as dev event which actually is the
hub for every dev event
you you mustn't miss. That's exactly
what we'll build. And we'll also be
using a bit more interesting fonts. So
let's go ahead and update these two
fonts that we have not gist and gist
mono rather we'll be importing now bear
with me when it comes to the title
sheisted grotesque. This is the font
we'll be using and we'll also add the
Martian mono. Okay,
from font Google so let's go ahead and
define them right here. This is the
whatever the name is uh grotesque. We
can use the camel case right here and
we'll use this name to create it. I'll
give it the actual variable name just as
the actual name is. And you can see my
webtorm is complaining about this not
being an actual word. Uh but believe me
it is. So we are going to save it to the
dictionary. Same thing for the grotesque
so that in the future it actually knows
that this is a real word. Okay. And the
same thing for the Martian mono. We can
call this font martian mono and we're
going to get the Latin variant of it.
Perfect. Now that we have declared the
metadata, we can change these two fonts
below by saying class name uh shipstead
grotesque as well as the Martian mono.
Uh perfect. And let's fix this uh
variable name right here. So it's
actually Martian mono. Um, so now we'll
be able to use these two fonts across
our application. And we can also add the
minh screen, which means that the whole
body will take the minimum of 100% of
the screen because everything should
actually fit in there. Perfect. So, we
can still see just the regular welcome
to Nex.js 16 test, right? To make it a
tiny bit more interesting, we can head
over to that page and switch it over
into an H1 tag, which will automatically
make it inherit our Tailwind styles.
Why? Because in our globals.css,
if you search for H1, we're applying the
following styles to every single
element. Don't worry, whenever we're
using some complex class names, I'll
always explain how these styles are
actually getting applied, just as we did
right here. And since we're working on
the layout right now, keep in mind that
our design has this very interesting I
don't even know how to call it like a
ray design. You can see rays of sun uh
some bluish greenish sun popping from
the top right corner. So while we're
working on the layout, since every page
has it, we can immediately go ahead and
implement it. For these rays of sun, I
found this uh very cool react bits
library which is a component library for
creative developers and I just want to
give them a quick shout out for this
component. You can see how the rays
actually follow the cursor and they
provide you with the full code for it.
So you can just Google for react bits
light rays or I'll leave the link down
in the video kit then head over to the
code and follow the installation and the
usage. I'll use the CLI installation.
So, it's going to automatically do it
through SHAT CN, which is another huge
advantage. So, switch over from manual
to CLI and copy the command and paste it
within our terminal.
It'll ask us whether we want to install
SHAT CN. That's for sure going to be a
yes. Then, when you get asked some
questions such as whether you want to
create a components JSON file, say yes
to that. And then, which color would you
like to select as the base color? Let's
go over with neutral. It'll add all that
info within our components JSON. Now,
what you can do is create a new
component within the components folder.
So, first generate the folder itself
called components and then a new file
called light rays.tsx.
You can go ahead and automatically copy
the code over from here. But to make it
fit a bit better with the design, I've
made some changes to the component and
provided the updated version to you
within the video kit down below. So you
can just copy it from components UI
light rays. So just go ahead and paste
it into this file we just created. You
don't have to worry that there's so much
code here. You can think of this as a
typical shadian component that you do
not touch. The only thing I've done is
added the top center offset which will
move the trays a bit so it looks exactly
as it does in the design. Now we want to
apply these light rays to the whole
application by putting them inside not
of the page because then we have to do
it for every single page but rather
inside of the layout because what
happens here will actually be translated
over to every single page that is within
this layout. So right here at the top of
the body, go ahead and copy the usage
part of the light rays from the
documentation and paste it right here.
You have to autoimp import it from light
rays component light rays. And we can
change how it behaves starting with rays
origin which I'll set to top center
offset. If you head over to your
application, you should be able to see
how it looks like. We definitely want to
remove this import statement. There we
go. And we don't need to put it into an
additional div. We just want it to be
there right within the body. There we
go. So you can see how the rays are
moving right now. And we can also change
the ray color. I found the color of hash
5DF ECA to work nice. It has this
greenish hue. You can play with the rays
speed. I'll set it to something like 0.5
for them to be a bit slower. The light
spread can be around 0.9. Ray length
will be about 1.4. Follow mouse can be
set to true.
Mouse influence I will lower it
significantly because the goal of this
app is not for the users to play with
the actual rays. Uh it is for them to
check out dev events. So whenever you're
implementing some animations, you want
to make them simple. The noise amount
can be set to zero and distortion can be
set also to a minimal amount with no
need to provide a custom class name. I
will put the rays into a div
like it was before, but I'll provide
some different class names such as
absolute. This is very important because
this will position the light rays
absolutely on the page, which means that
it'll not interact with the rest of the
elements on the page. I'll also insert
it to the top by saying insert zero and
top zero. Giving it an index of Z minus
one. So that way it appears behind the
elements with a min of screen.
Perfect. And I'll also give the children
the main tag. So they'll be wrapped
right within the main of the
application. That is where we'll load
the actual content of the page. Perfect.
So, welcome to Nex.js. The rays are at
the top. Finally, within our page, to
make it look a bit nicer, we can wrap
this into a section. We can give it an
H1 within that section that'll have a
class name equal to text-c center, and
it'll say the hub
for every dev. We can add a break tag in
between
event. you can't miss similar to what we
had in our layout.
And then we can have another P tag right
below it that will say something along
the lines of hackathons,
meetups, and conferences
all in one place. And I like it how
WebStorm actually catches the typos, not
just code mistakes. So I can just hover
over it and automatically fix it. And
I'll give this P tag a class name of
text-c center as well as a margin top of
five to divide it a bit from the main
title. Okay, we have the main content,
but what happened to the styles? It
seems like we don't have that dark
background we used to have. That's
because after we installed this package,
it went ahead and modified our
globals.css. So just once again go ahead
and copy the final one. Head over to the
globals.css file and override it. If you
do that, you'll notice that the styles
will be there. And you'll also see very
light rays at the top. But if you extend
it to full screen, you can see that
they're much more pronounced, but still
not taking away from the overall
experience of the application. So just
like that, we've successfully set up our
project and we can already see something
on the screen. In the next lesson, let's
develop even more parts of the homepage
UI.
Let's start developing the other parts
of our homepage. Starting with this
explore button, we can create it as a
new component. Right here within our
components folder, I'll create a new
file called explore
btn.tsx.
We're creating a separate component as
button is associated with event
listeners that have to happen on the
client side. This kind of thinking is
necessary when developing NextJS
applications because it's all about
serverside rendering. See, I'll run
rafce right here to quickly spin it up
and you'll get a better idea of what I'm
saying. This component will be a button,
specifically a button that we're using
within our page. So, let's immediately
go ahead and import it below our P tag
explore btn as a self-closing tag. There
we go. You can see it right here. Now,
keep in mind that our homepage is
serverside rendered. We're getting all
the benefits of SEO boost as well as
quick load times, but we still need some
kind of functionality for this button
because buttons will eventually have
onclick listeners where for example, we
want to perform an action. For now, that
action can be just saying that we
clicked it. But as soon as you give it
an on click listener, that means that
you have to turn it into a client
component. So that's the reason why I
turned this button into its own
component. So now I can add this use
client directive at the top. You can
read a bit more about server and client
components within Nex.js JS
documentation where it explains how they
work and how these are basically two
entirely different contexts and
environments.
Client refers to browser on a user's
device that sends a request to a server
for your application code and the server
refers to the computer in a data center
that stores your application code.
What's important note that each has its
own set of capabilities and constraints
and I dive super deep into this within
the ultimate Nex.js GS course where we
cover application navigation, the state
management and how we can handle it on
the server side through URL state
management and finally diving into
thinking in backend when it comes to
Nex.js applications. Same things goes
for server actions. How do they actually
work and how do they work on the server
specifically? And then of course
different approaches when rendering
components whether on server or on
client. But with that in mind, we now
have this button that is a client
component inside of another server
component. So this is rendered on the
server. But if you need some extra
functionality, you can render it on the
client. That's all I want you to know
for now. So we can give this button a
type equal to button an id equal to
explore btn with a class name of margin
top of seven to divide it a bit from the
content as well as the margin x of auto.
Within this button we can render an
anchor tag to make it a link pointing to
hash events which will show below the
button. It'll say explore events
and right below it we'll actually render
an image. So this is a Nex.js image. So
we can import it from there with a
source of for/icons/
arrow-down.
SVG with an al tag of arrow-down.
And we can close it right here. We'll
also have to give it a width of
something about 24 and a height of 24 so
it actually appears as it knows its
dimensions. There we go. Explore events.
And it looks like my styles were not
getting applied. So I simply repasted
the final globals.css into the public
and resaved all the files. And it looks
like right now we're good. And I just
want to recap this situation one more
time because it might be
counterintuitive and in this crash
course I don't want there to be any
confusion. The goal is to make this the
best Nex.js crash course you watched
with all the questions answered. So if
at any point you're confused, leave the
timestamp of where you're at at the
video in the comment and then write your
question and I'll clarify it. That way
we'll be helping out each other. I mean
that's kind of like what we're doing in
the ultimate NexJS course uh where in
the architecture lesson if we dive into
client versus server paradigm there's a
complete lesson on the topic and then
below that lesson we have the comments
for example Robin was confused between
serverside rendering and server
components and that allows us to provide
a much more detailed response to his
question specifically as well as other
people can ask questions or just
contribute to the overall learning
process. Okay, but why did I even start
talking about this? Well, that's because
it might feel super weird that we have
already split this explore button into
its own component. Usually, you won't
start by building client components
right away. Instead, you'll create them
as you encounter specific client side
needs, such as the need for an onclick
listener right here. In many cases, most
of your features will run on the server
side, but there will be many times where
certain parts just need to be
implemented on the client. And that's
where this architectural mindset becomes
incredibly valuable. It'll help you
decide exactly which parts should be
rendered on the server and which should
be handled by the client instead of just
defaulting to one side. I'm happy to say
that that's also covered within the
ultimate Nex.js course. Here's a lesson
summary and its full transcript. And
people in the comments are also
discussing how this works on different
types of largecale applications such as
within an e-commerce app. So if you
haven't already, definitely explore the
ultimate Nex.js course now bundled with
the complete testing course. So you can
also later on test these server and
client components. I'll leave a special
discount link down in the description.
But now let's develop the rest of the
homepage.
Below the button, I'll develop another
div. That div will have a class name
equal to margin top of 20 and space Y of
7 to give it some breathing room. And
it'll have an H3 that'll say featured
events. Below it, we can map over a
couple of elements um in an unordered
list with a class name of events where
I'll create a new dynamic block of code
where you can create an array that'll
just have numbers from 1 to 5 and we can
map over them. Each one of these numbers
will later on present a full object
containing that event's data and then we
can automatically return a new list
item. When I say automatically return, I
mean not opening up a function block
with curly braces here, but rather just
putting parenthesis, which means that we
don't even have to define return.
Rather, it returns it by default. Since
we're mapping over this, we have to give
it a key equal to event. And I'll say
event
event. So it should say event 1 2 3 4 5.
So let's create a new component in the
components folder. and I'll call it
navbar.tsx.
Within it, we can run rafce to quickly
spin it up. We'll use semantic elements
such as header in this case and within
it a nav. This helps screen readers and
in general improves your web app and its
SEO because browsers better understand
the elements on your app. Then I'll
render a link component right here. This
link is coming from next link. So you
can automatically import it. And I'll
make it point to forward slash which is
just the homepage with a class name of
logo
within which we can then render an image
also coming from next. It's going to be
forward slashicons/lo.png
PNG, which is the path to the actual
logo with an al tag of logo, a width of
about 24 as before and a height of 24 as
well. If you save it, you won't be able
to see anything yet, of course. That's
because we haven't yet put the navbar to
use. We'll do it very soon. Just after
we add this P tag that'll say dev event,
this will appear next to the logo so
people know the name as well. And below
the link, I'll display a UL, an
unordered list that'll contain
additional links. The first link will
point to homepage for now and it'll say
home. We can duplicate it two more
times. The second one will point to the
events page and the third one to the
create event form.
Now, if you save this, we can actually
use this navbar in our layout, not in
our page, because the layout will make
it appear across all of our pages, which
is another great lesson to be taught
here. Whenever you're seeing something
that appears across multiple pages, such
as this navbar appearing here on the
homepage, on the details page, and on
the admin dashboard, then it's good idea
to put it in the layout. But if
something is appearing just on the
authentication page for example, then
you can put it on that page directly.
These are also some principles taught in
the ultimate nextgs course. Before I
showed you the curriculum, but now I can
actually show you the full app that
we're building as part of that course
and it's Dev Oraflow, the more advanced
clone of the full stack orflow
application with complex sorting,
filtering, search and yeah, here you can
see that we have the global search right
here which searches across all different
types of data in our database such as
questions, users, answers and so much
more. So, for example, on our profile
page, we can still have this global
search and it's also shared across all
the other pages such as collections,
community, homepage. It's everywhere.
But, for example, it's not on the login
and signup pages. That's because these
two use a completely different layout.
So, with that in mind, let's put this
navbar to use right here above our div
containing the light rays. I'll simply
refer to it as navbar and import it from
the proper place. It should look
something like this. On smaller screens,
we can see just the logo and the links.
But when you enlarge it, you can see a
more beautiful version of our dev event
application. Now, let's turn these
boring events, which are just pieces of
text, into actual reusable events cards.
We can do that by creating a new
reusable component in our components
folder which you can call event
card.tsx.
Run rafce to quickly spin it up and then
this component will accept all of the
data that it needs to show as props
because each event card will show us
completely different data. Let me show
you what I mean. We can first make it
accept um well let's do a title and an
image.
These will be of a type props.
We're using these props so TypeScript
knows what this component is accepting.
So right at the top we can say interface
of props and make it accept a title of a
type string as well as an image of a
type string as we'll pass over a URL.
Then we can immediately return a link
because each one of these cards will be
clickable pointing to the full events
details page by having an href pointing
to for example for now we can leave it
as just forward/events. We'll change it
later and an ID of event card within it.
We can display an image with a source
equal to image and right below it a p
tag where we're rendering title with the
class name of title. Don't forget to
import the image coming from next image.
And we also have to give it the alt prop
which is going to be rendering the
title, the width of about 410,
the height of about 300, so it looks
like a banner format and a class name of
poster. Now that we have created this
reusable event card, we can head back
over to our page where we're mapping
over these four events. And instead of
just mapping over the numbers right now,
we can actually create a new events
array which will have two objects for
now. It can have a first one that has an
image of for/im images/vent1.png
and it'll have a title of event one.
I'll duplicate it to create the event 2
and event 2.png respectively. Now we can
map over these two events by saying
events.m mapap. And this time we won't
just be rendering the event number
rather we'll be rendering a reusable
event card like this. That still will be
within its own li. So I'll create a list
item with a key equal to event.title
title and within it we'll render the
event card to which we will spread all
of the event properties like this.
Immediately if you do that you'll be
able to see two new featured events
event one and event two showcasing their
images. Now we have to focus on passing
some other important event data as well
to make each event even more
distinctive. For this, we can refer to
our design like what other important
pieces of info does each event have? So,
we can create those events in the
database later on and then use this
reusable event card component to render
completely different pieces of data. Of
course, we have the location, this one
right here. Then there's the image and
the title as well as the date and the
time. And finally, we need some kind of
an ID or a slug allowing us to redirect
to that events details page. So, let's
go ahead and define those pieces of data
right here. Alongside the image and the
title, we'll also have a slug that's
going to be something like event one.
We'll have a location equal to location
one
as well as a date set to date one and
finally a time set to time one. Pretty
unimaginative, right? But I think this
is really the part where it makes sense
to include an AI coding agent that can
help us when we need help. I typically
use Juny for much more complex tasks
because it can really do a lot. But in
this case, we'll use it to generate some
meaningful data for our events. I'll
leave the link down in the description
so you can download it and use it
alongside WebStorm. It is super
convenient. I press commandshiftp and
just search for Juny. It immediately
pops up right here. You get examples of
what you could ask it to do. But in this
case, we can just ask it to create a new
file which is going to be in lib
constants in the root folder. It should
export an array named events containing
real upcoming conferences. I'll leave
this snippet so you can copy it in the
video kit down below and you can just
paste it here. Now, just before I let
Juny generate things, see here that I
said that we need to make sure that the
data looks realistic and can be directly
imported and used in the event card
component. Since I said this, it's very
important to dive into the event card
and make sure to update the props so it
knows exactly which fields we're
expecting to use. This will include the
slug that we created, also the location.
It'll include the date and finally the
time. TypeScript is super useful not
just for us, but also for AI agents to
know the structure of the application
and to just align the expectations. Now
that this is done, we can go ahead and
let Juny do its job. This typically
takes about half a minute, so I'll let
it do its work and I'll be right back.
And even sooner, it created a new
constants file, so we can head over into
it directly within lib constants. There
we go. Curated list of upcoming popular
dev events. It created and exported an
event item so that we can now easily
import it. So we can easily use it
across whenever it needs to be used. And
then there's some other events right
here. So let's go ahead and easily use
them by heading over into our page.
Instead of creating our own array of
events, we can now very easily just
import these events coming from lib
constants. So we're not cluttering our
view right here. And just like that, you
immediately get all of these other
events listed right here. So now we can
head over into this component to display
all of these additional pieces of data
that we now have. Let's first get all of
these props such as the slug, the
location, the date, and the time. We
will use the slug right here in the URL
so we can point to it. In between the
image and the title, I will also display
a div that'll display the location. So
this div will have a class name equal to
flex flex- row gap of two and we'll
render an image that has a source of
forward slash icons/pin.svg
SVG with an al tag of location. A width
very small of about 14. Height can be
set to 14 as well. And then just below
it, I'll display a P tag. That'll render
the location. There we go. Some are
happening in San Francisco. This one
says Vienna, but I believe it was
actually in Croatia.
There's also Los Angeles, Paris, and
Vancouver. Alongside showing the
location, let's also display some
additional date and time information
below the P tag. So, I'll create a div
with a class name of date time.
And within it, I'll display a div
that'll be very similar to this div.
It'll contain an image for the icon and
then a p tag for the actual info.
But instead of it being a pin, it'll be
a calendar with the al tag of date. And
then this will render the date. The date
is here. Now I can duplicate this div
just below. And this time we'll change
it over to clock for the time. And then
we can render the time. Perfect. This
now feels like a real event card. So now
if I expand our screen, take a look at
this. The navbar looks great on desktop.
So does the hero section. We can explore
the events and then there's a list of
all of the most popular dev events
around the globe. Looking good already.
Now this is the point in time where we
actually have something tangible and
visible on our app. So what do you say
that we follow the best practices of
writing code in general and actually
ship this code over to GitHub? It looks
like GitHub is preparing for their own
conference, but I've closed that because
we have our own app that tells us about
dev events. So now let's go ahead and
create a new repo. I'll do it on my
personal profile and I'll call it dev
events
next.js 16 crash course. Let's go ahead
and create the repo. Once you do that,
you'll be given the steps needed to
connect it to your local repo right now.
So let's just follow them by opening up
the terminal and running get init
commit-m
first commit get branch dash m main to
open up a new branch and then get remote
add origin with the URL of your repo and
finally get push u origin main. This
will take all the existing code and push
it over to GitHub. While we're here, we
can also provide some additional info by
copying it from our layout. The hub for
every dev event you must not miss.
That's the description. For the website,
we'll be able to put it later on once we
actually deploy it. But for now, I'll
say jsmastery.com.
For the topics, we can say Nex.js and
Nex.js16.
And we can hide these to make our repo a
bit cleaner, but I'll make sure to add
https
right here to the URL and save it.
Perfect. Now, as we continue developing
this application, and I would highly
advise you to do this for all the
upcoming applications,
always push over to GitHub. It's super
beneficial for so many reasons. Anybody
can head over to your GitHub profile and
check out your contribution graph. The
more you push, the more active you are.
Your projects can also get noticed. And
most importantly, potential employers
are going to look at your commit
history, not your browser history. Don't
be afraid. Just your commit history
where we need to make sure to use proper
commit names. So for every single
feature moving forward, we will not only
write proper commit names, but we'll
also be opening up different branches
and pull requests so we can verify that
the code that we actually write is of
high quality. Code rabbit here will be
of great help as it'll help us cut that
code review time and bugs well in half,
right? So starting from next feature
onward, you, me, and the rabbit will be
reviewing all of our code. So, click the
link down in the description to start
with a free trial of Code Rabbit. It's
also available in many IDEs and one of
their newer features are CLI reviews,
which means that you can get instant
code reviews directly from your terminal
while you're developing the app. We'll
explore all that and more very soon. But
for now, just so we don't forget, let's
immediately create a new branch for the
feature that we're going to start
developing next. And that is
implementing Post Hog. So you can say
get checkout to immediately move to this
new branch and dashb to automatically
create it implement post hog. There we
go. If you've done that properly, you
can just switch over to this new branch.
Implement post hog. And I think that
gives you a pretty good idea of the
feature that we'll develop next. So
let's do it.
Now that our homepage is up and running,
it's time to shift our focus from what
we've built to how do people actually
use it? Because here's the thing, even
the most beautifully designed app isn't
truly valuable until we understand how
users interact with it. Are they
visiting the pages we expect them to
visit, or are they just randomly
clicking around? Where are they dropping
off? Are there errors happening behind
the scenes that we are unaware of?
That's where Postthog comes in. It's an
open- source analytics and product
insights platform that helps us answer
all these questions and more. With
Posthog, you can automatically track
user behavior, see how different
features are performing, monitor errors
in real time, and even capture valuable
metrics that guide future development.
And I'm actually including this step in
the project because this is how
realworld production apps are built.
It's not just about writing code. It's
about creating something that evolves
based on user feedback and data. But you
might be wondering why are we setting
this up now right after building the
homepage and before diving into the rest
of the features. The reason is simple.
Analytics are most valuable when they're
a part of the development process from
the very beginning. By integrating Post
Hog early, we ensure that every new
feature we add will be measurable and
trackable from day one. This approach
reflects how real world SAS products are
built. Heck, the entirety of
jsmastery.com
is fully tracked by Post Hog. Seeing
what you guys do on the platform, where
you click, how useful specific features
are matters so much to me. So that's why
tracking these data pieces with Post Hog
allows us to continue shaping the
platform. based on user behavior, not
just intuition. So, in this lesson,
you'll learn how to set up Post Hog
inside of our dev events application,
and together we'll explore its features.
This will allow you to integrate Posthog
into every single one of your upcoming
Nex.js applications to make them
intelligent datadriven platforms because
the process is more or less the same.
So, let me show you. To get started,
click the Post Hog link down in the
description. And I mean, just check out
this super unique landing page. I'll
have to just ignore how cool it is for
now and just show you how we can get
started. So, click this link right here.
You'll have to choose your region and
then sign up through one of the
providers. It'll ask you to create a new
organization. So, type JS Mastery. Then
you can select your role. And if you
have a second, you can also tell this
hedgehog that you heard about it from
me. So, say Adrien or a JS Mastery is
fine as well. and let's create the org.
Now it'll ask you which products you
would like to use. You can select
whatever you want. I want to use product
analytics, web analytics, data warehouse
will be useful, session replays. Later
on in upcoming videos, we might even
dive into feature flags and experiments.
Surveys are great if you want to just
get a quick MPS score or user
satisfaction score. And then of course
air tracking. That's what we're here
for. So, I'll actually go ahead and
select everything starting with product
analytics. Let's go. I just realized
that we're still looking at a light
color theme. So, at this point, you can
just switch it over to dark mode. That's
much better. Now, the next step is to
install Post Hawk to make it run with
our application. Believe it or not,
Nex.js is at the top of the list of all
of these frameworks, and that's exactly
what we want to use it with. As I told
you, this course will teach you how to
use Post Hog with every single one of
your upcoming Nex.js applications. Now,
you can copy this AI setup wizard
command. This AI wizard should install
and configure Post Hog automatically.
So, here you have to choose your Post
Hog cloud region. It'll try to
authenticate you. It'll install
PosthogJS, Post Hog Node, review the
documentation for Nex.js, create all the
necessary files, and basically set it up
for you.
If you're using an AI powered IDE,
you'll also be asked if you want to
install the MCP server to use Post Hog
in your editor. That will teach your AI
how to use Post Hog. In this case, I'll
skip it, but that's definitely something
we can explore in the next video. And
that's it. Successfully installed Post
Hog. What this installation did is it
created a new file called
instrumentationclient.ts.
and it also added Post Hog's environment
variables to yourv file. Now, as soon as
Post Hog captures the first event within
your application, it'll let you know
that on the setup page. So, if you head
over to localhost 3000 and try clicking
around a bit, you'll see that it auto
captured an event a few seconds ago. And
with that, the installation is complete.
So, let's continue. Here, it's asking us
what we want to use Post Hawk for to
auto capture front end interactions.
enable heat maps, enable web vitals, and
enable session recordings. I'll say yes
to all because these are all great
features that we don't want to miss.
Let's continue. And now I think this is
the best part. There's both a completely
free plan that allows you to have one
project and then there's also the pay as
you go plan, but with a free tier
included, which starts at zero bucks per
month. And I'm not sure if I'm allowed
to say this, but Post Hog's free tier is
the best in the game. I really mean it.
It is highly likely that if you're using
Post Hog on your personal projects,
you'll never cross the free tier. If you
start using it on your apps that grow
and have so many users, then it's still
super cheap, but then we're hoping that
you're making money off of this app,
right? So, for now, I'll go ahead and
proceed with the free plan. You can
invite teammates if you want to. And
we're in. Now, I've zoomed it out a bit
so you can better see what's happening
here on the screen. On the left side,
there's a left sidebar allowing you to
get to different parts of your
dashboard. In the middle, you have this
browser-l like explorer where you can
see all the different insights. And then
on the right, there's the quick start
guide where you can learn everything
about Post Hog. I would actually advise
you to go through it to learn a bit more
about how it works. For now, I'll skip
inviting a team member because we're
working on this solo. And then we are
ready to create our first insight. So
click on that. What this allows you to
do is to analyze user behavior patterns
in your product. You can visualize
events, actions, and properties to
understand how people use your app. So
let's start by quickly creating a new
insight. Even though we don't yet have
too much data captured, we can already
set up a simple page view inside to see
how many people visited our app up to
this point. So right here at the top you
can say home page views. Then select the
page view and the total count and switch
this option over to a cumulative line
chart. Once you've done that, click
save. And that's your first insight. So
now if you head over to the homepage,
you can see some insights created for us
on your dashboard. But you can also head
over to dashboards and create a new
dashboard that'll have access to this
new insight that you created. So
insights go onto dashboards. There we
go. You're now tracking general app
usage. That can be the name of the
dashboard. And you can see that we have
about four page views since we set up
post hog. Now another thing I want to do
right from the start is to enable error
tracking. So on the left side sidebar
under behavior you can click on error
tracking and enable exception auto
capture. Now later on when you visit
error tracking we'll be able to see all
the errors that are happening. And just
like that you now have post hog
successfully integrated within our dev
events project. This is the first step
toward building truly datadriven
applications. An app that doesn't just
function but actively learns, adapts,
and improves based on how users interact
with it. But this is only the beginning.
As you develop more features and grow
your platform, we'll revisit Posthog
again and again, adding custom event
tracking, building some new dashboards,
and diving deeper into product
analytics. So, with every new feature,
you'll make your app that much smarter,
more intentional, and closer to the kind
of the productionready applications that
companies rely on in real world
projects. Great work.
In this lesson, you're going to learn
how to integrate MongoDB within your
Nex.js applications. It's a highly
flexible document-based NoSQL database
that pairs extremely well with modern
frameworks like Nex.js. MongoDB stores
data in JSON-like documents, which makes
it a natural fit for a JavaScript and
TypeScript environment. You can
structure your data exactly the way your
front end expects it without complex
migrations or rigid schemas. Its ability
to scale horizontally, handle
unstructured or evolving data, and
perform lightning fast queries makes it
ideal for projects that need to grow
quickly. That's why I've chosen MongoDB
for this project. It's easy to get
started with and easy to grow. So head
over to MongoDB Atlas and click get
started. Then create a new account or
simply sign up with Google. Once you're
in, head over to your organization and
within it create a new project.
I'll call this one devvent next.js crash
course
and just click next. Put yourself as the
project owner that you are and create
the project. After you have the project,
we need to create a cluster, which is
basically your database. For this,
you'll have to choose the deployment of
your cluster. Don't worry, you don't
have to pay anything. Just select the
free version, which is going to be more
than enough. Select your region that is
closest to you, and click create
deployment. Now, you'll have to create
your database user. Simply choose your
username and password, which you can
copy, and then create a database user.
After you've created the user, choose a
connection method. In this case, we're
going to connect directly with our
application using MongoDB's native
driver, such as Node.js. We'll very soon
install MongoDB within our application,
but for now, let's wait until our
cluster is provisioned. This can
typically take a couple of minutes. When
it's done, you'll be able to see a
copyable connection string. So, just
copy it. And then back within your
application, head over to yourv and name
it mongodb
urri and make it equal to the connection
string you just copied. Make sure to
replace the password with the actual
password you generated for your MongoDB
user. With that, your serverless MongoDB
database is ready to use. So in the next
lesson, you'll start using this database
to implement the core features of dev
events.
Before we dive into implementing
database operations in our Nex.js
application, I want to stop and show you
a tool I've been using a lot lately.
It's Warp, an all-in-one AI powered
development environment built to help
you build faster. Instead of memorizing
commands and hopping between tools and
documentation, just describe what you
want in plain English, and Warp will
understand, execute, and deliver these
results instantly. And the best part is
the developer experience you get from
having everything together. A terminal,
code editor, and agents all in one
place. No need to have three different
browsers, one editor, and 10 open tabs
just to get some output from an AI. When
it comes to pricing, Warp also has a
100% free plan, so you can check it out
before making any commitment. For a
limited time, specifically for you
watching this video, you're getting Warp
Pro plan for only $1, which is normally
18 bucks per month. So, click the link
down in the description and grab it
before it's gone. Let me show you how we
can set it up. Click the link down in
the description and download it for your
operating system. Once you open it up,
it'll look a bit empty, but that's for a
reason. Soon, you'll be able to open all
the different things needed for you to
develop apps right here. Here you can
select from different agents. Auto mode
is pretty cool and you can even tweak it
how you like it. But there are more
advanced models like GPT5 or Claude 4 or
4.5 Sonnet which are right now one of
the best models when it comes to coding.
And don't forget that warp is actually a
terminal. So you can just navigate over
to our repo. So just cd into Nex.js
crash course and optimize warp for this
codebase. It'll actually let the agent
understand the codebase and generate
rules for it. So just click optimize and
we'll say index. No need to generate the
warp md file for now. Perfect. We're now
on the implementing post hog branch. And
actually this reminds me that we've
finished the post hog implementation in
the previous lesson. So we might as well
commit it. I'll say get add dot get
commit-m
implement post hog and run g get push
specifically since we created a new
branch we want to set upstream to origin
to push it to our actual remote repo. A
pretty cool warp feature here is that
you can just press command enter and
it'll automatically fix the command up
for you. Uh which is absolutely amazing.
You can just say run. There we go. The
branch is now pushed. So, if you head
over to GitHub, you'll see that there is
a new pull request to be reviewed. Go
ahead and create it. We want to merge it
straight into main. And in the upcoming
lessons, we'll use code rabbit's help
here to review this PR, but this one is
super simple. We basically didn't write
any code besides following along the
post hog implementation guide. So, for
now, I'll just go ahead and automerge
this one. Now the latest code is all on
main which means that within our code we
can now check out to the main branch
pull the latest changes by running get
pull and we can create a new branch
which is going to be all about what
we're going to do next. It'll be about
implementing database models. So say get
checkout-b to create this new branch and
call it database models. And you can
immediately see that warp offers itself
to fix a bug or implement a feature
within our database models branch. Slow
down there, cowboy. First, I want to
teach you guys how to think like a
system designer to think about what we
need, which data pieces do we need to
model, and how our application database
structure will look like. Only then can
we bring in warp to help us. So, let's
do that next.
Before you touch a single line of code,
pause and think like a systems designer.
Data modeling isn't about typing out a
schema. It's about understanding the
problem domain and asking the right
questions until the structure of your
data almost designs itself. So start
with the most fundamental question. What
is the main entity in this application?
For dev events, the answer is clear.
It's an event. Everything else revolves
around it. Now ask, what defines an
event? Well, it needs a title. That's
the name users will see. It needs a
description and an overview so people
know what it's about. It probably needs
an image to make it visually appealing.
Where is it happening? That's the venue
and location. When is it happening?
That's the date and time. But those need
to be stored in a normalized consistent
way so you can easily sort filter and
query them reliably. Maybe also how is
it happening like the mode is it online
offline or hybrid who is it for like
that's the audience and what's going to
happen there maybe like the agenda or a
list of topics or activities maybe even
who's organizing it right if it's a
well-known company like Microsoft or
Google that can be there as well. Also,
if you're advanced, you might want to
think about the organization of how you
want to categorize it so the people can
search it more easily for later. That's
where tags would come in. And for
SEOfriendly URLs, you'll want a slug,
which is a unique URL safe identifier
generated from the title. Every one of
these fields answers a realworld
question and together they describe
exactly what an event is not just for
your code but for your product. You're
designing them based on realworld
requirements. That's how data modeling
should always start. But that's only
half the story. The next question you
should ask yourself is how do users
interact with these events? Well, you
could say that they sign up for them or
that they book them. So, we need to
introduce a second entity, booking. So,
for that, you also need to ask yourself
the same questions. What do we need to
store for a booking? We need to know
which event was booked. That's a
reference to the event ID. We need to
know who booked it. An email address
will be enough. And we also need to
track when the booking was made. We can
do that with automatic timestamps like
created at or updated at. This simple
relationship, one event to many
bookings, forms the backbone of our
platform's logic. And while this might
feel small and focused right now,
remember that this is just the
foundation. In larger production scale
systems, a booking could evolve into
something much more like a payment
status, different ticketiers, user
profiles, cancellation policies,
refunds, and more. This is your starting
point. And from here, you're free to
grow it in any direction you want. But
the one thing I want you to get is the
mindset of asking what should the data
do rather than just what data do I need?
This is the foundation of back-end
architecture. You think from the real
world behavior you're trying to
represent and design the database so
that it naturally reflects that
behavior. That's how you go from tables
and fields to systems and logic. And
that's exactly how largecale
applications are built. And all this is
simple architecture. If you want to
learn about more advanced data modeling
on a real production level app and
understand that systems level thinking
in depth, that's exactly what we do
within the dev overflow application.
Starting from thinking in backend,
architecting database structure and then
slowly creating all of these different
models within our application with some
active lessons allowing you to actually
think and approach things on your own
with some hints in order to be able to
truly understand how that architecture
mindset works. And once you start
thinking in that mindset, the rest will
become much easier because now the
schema is just a technical requirement.
And whether you're building a small side
project or architecting a massive
production system, the same mental model
scales with you. By now, you've done the
hard part, the thinking. You've answered
all the key questions, mapped out the
relationships, and defined how your data
behaves. So that's exactly where the
modern AI development takes over. You
don't have to necessarily write all the
boilerplate code by hand. Instead, you
can use AI to handle the repetitive work
of generating these mongoose schemas,
TypeScript interfaces, validation logic,
and even pre-save hooks. That's the
power move. You architect, AI
implements. That's exactly how
professional engineering teams work
today. They focus their time and energy
on designing systems and let automation
handle the rest. And now you're going to
learn how to work exactly like that. You
can think of this segment as an
additional course within this nextjs
course that teaches you how modern
engineers are using AI to their
advantage. So head back over to warp.
First things first, we'll use it just as
the terminal to run mpm install
mongoose. That was quick. And now we can
use the same interface which a second
ago was a terminal to write an actual
command for an AI model to execute. I
took some time to write it in a detailed
way so we both get the same output and
you can find this exact text within the
video kit link down below as a copyable
snippet. Let's go through it together.
You're a back-end developer working on a
Nex.js application with Mongus and
TypeScript. Your task is to create a new
MongoDB.ts file in the lib folder of a
Nex.js application. Set up a mongus
database connection to MongoDB using
TypeScript with proper types. Cache the
connection to prevent multiple
connections during development. Write
clear and concise comments explaining
key parts of the code and making sure
that code is clean, readable, and
production ready. It's quite an
open-ended task, so let's see how it
does. I'll press enter, and I'll say
start a new conversation. It's now
figuring out what it needs to do and it
says that it'll create that file for us.
There we go. It gave us the file right
here so we can review it and potentially
make some changes. I'll just press enter
to accept it. And that's it. The file
was created. You can now open up your
project explorer by pressing
commandshift E and head over to lib
MongoDB.ts
to explore this file. I'll zoom it out a
bit so we can see it a bit better. And
what happens here is it defined the
connection that we can cache because
Nex.js by default is not keeping a
consistent connection. Rather, every
time we make a server action call, it'll
spin up the server for that amount of
time needed for it to execute that
server action. Then the next time we're
trying to call it, we don't want to
generate a whole new connection. Rather,
we want to grab the connection from the
cache. And that's exactly what it is
doing right here. It's returning the
cached connection. And finally, it is
connecting us to a database using the
MongoDB URI from our environment
variables. With that, we should now be
able to connect the app to the database.
Oh, and just so I don't forget, on the
left sidebar in MongoDB Atlas, head over
to IP access list and then add an
additional IP address by clicking allow
access from anywhere. This will make
sure that we can connect to our app
after deployment without any issues. Now
back within warp, we want to give it
another task. So you can copy and paste
another snippet of code within the video
kit down below and we can go through it
together.
This time the task is to build a
database layer with two mongoose models
event and booking just as we discussed
in the architecture part of this lesson.
Keep in mind that here I'm being super
precise. The only reason why I'm doing
that is so that both you and I have the
same output in the code. So everything
is super clear. Otherwise, if I weren't
doing this for the video, I would have
written a much much shorter command. I'm
telling it to create exactly three
files. Event model.ts,
booking model.ts, and an index
file.exports both of them. So in the
event model, we want to create a
strongly typed mongu schema and model
called event with the following fields.
All of which we have discussed and
architected before. I also give it some
requirements such as to create a
pre-save hook which is basically a
function that will take a title and
generate a URL friendly slug so we can
use it later on. And I'm also telling it
to use the automatic timestamps.
Then we want to generate a booking
model. Same thing here but much simpler.
And then finally in the database index
DS we're exporting both the event and
booking models so they can be imported
anywhere within the application from a
single file. So press enter. Looks like
it automatically figured out what it
needs to do. Typescript and mongus are
installed. If mongus hadn't been
installed, it would go ahead and install
it as well for us. But now it'll just
proceed with creating those files. I'll
go ahead and accept the first one.
That's for the event model. Then we can
review and accept the second one, which
is for the booking model. And finally,
one that exports both of them alongside
their TypeScript interfaces. It'll even
run a TypeScript check to ensure
everything is properly typed and valid.
Perfect. Oh, another thing is that it
self-fixes the linting errors, which is
pretty cool. Wonderful. It is done.
Three files have been created. And now
we can go through them together to make
sure that we fully understand what is
happening. I'll open up the file
explorer and head over to database event
model.ts.
I'll expand it and we can go through it
together. First, we have the TypeScript
interface for this event model covering
all the important pieces of info that
our model will have. But then more
importantly, we're actually creating an
event schema using Mongoose schema that
is strongly typed to this event. so that
at any point in time our application
knows exactly which properties it needs
to accept. We have a title which is
required with a max length of 100, a
slug, a description, overview, and
everything we discussed before. Now, if
you head down, you'll see that there's
also this pre-save hook for slug
generation. So, before we save it, it's
actually going to take the title and
generate a slug for it. And here's a
helper function that generates the slug.
Is it an overkill? And could we have
done it in a simpler way? Well,
definitely yes. But this is more robust
and I'm actually glad that we're able to
get to this so quickly. There's another
function that allows us to more easily
get access to the date in a human
readable format. Perfect. Now, alongside
that, you can also check out the
database booking model, which is much
simpler. Here we have the event ID,
which is actually a reference to a real
event in our database. We have an email
with validation function. And then also
before we save it, we want to validate
that the event exists before creating
this booking. We also have some indexes
for faster and easier queries. If I
didn't use warp and if I was writing all
of this by hand, I would most likely be
a bit lazy to write this good enough of
a validation function for events. Or
maybe I wouldn't have remembered to
write these queries or indexes. And
these are the things that make using AI
as a helper great. What we can do now is
push this over to GitHub by running git
add dot getit commit-m
implement database models and get push.
Once again we'll have to set the
upstream branch to push it over to a
remote repo. Perfect. It's pushed. I
know it seemed like we have done a lot
right here or like a lot was done for us
but at every single point in time you
need to fully own your codebase. You
need to understand 100% what's
happening. So in the next lesson we'll
do a bit of a recap and review what we
have done just now.
Head over to your GitHub repo and you
should be able to see that our database
models branch had recent pushes a couple
of seconds ago.
that allows us to compare it to the main
branch and potentially open up a PR,
which is exactly what I'll do. Now, this
was a bit of a larger PR, which I
definitely don't want to go ahead and
just merge domain. Rather, I want to do
a proper analysis of what we did within
this PR. We implemented the booking
model, the event model, the database
index that exports both of them as well
as set up a MongoDB connection and
installed mongoose. Now, instead of
going through it line by line, I
actually want to put code rabbit to
work. As you can see, it says waiting
for status to be reported. Review in
progress. And it seems like it started
processing the changes in this PR. This
may take a few minutes. So, let's give
it some time and a fist bump and I'll be
right back. I just went to grab a coffee
and when I got back, here's the summary
created by Code Rabbit. Uh, new features
that we implemented here. Uh, it's going
to be the event management system with
comprehensive event details containing
all of these important pieces of info
such as the title, description, date,
time location audience and
information. and the booking system for
event registrations featuring email
validation and automatic timestamp
tracking for registration records. We
also added the MongoDB database support.
Here we can go through a more detailed
walkthrough where we're introducing
MongoDB and Mongus integration with the
new event and booking models featuring
schemas and validation indexes and
pre-save hooks. What I like most about
it here is the sequence diagram. Yep, it
actually created a diagram of what
happens within our application. So this
is the app talking to its inner
workings. The application makes the
connection to the database. Then it
checks for the global cache whether we
already have an existing connection.
If there is an existing connection, it
returns it and we have the MongoDB
instance. But if we're working on a new
connection, then it establishes it,
connects it, returns it, stores it in
the cache so that the next time we can
easily retrieve it. The time it would
take to review this PR is about 30
minutes. But thanks to code rabbit, we
didn't have to spend a second. And just
like that, our rabbit now hops through
MongoDB with glee. Events and models now
stored with key. Uh, this is absolutely
perfect. Uh there are a couple of
nitpick comments and let's see whether
code rabbit actually caught some issues.
It seems like there is a major potential
issue. Prevent duplicate bookings per
event ID or email with a unique compound
index. So what is this actually saying?
Well, right now we're doing pretty good
work because our schema has indexes that
help us make our database queries
faster. For example, one for finding
bookings by event ID, another one for
finding bookings based on the creation
date, and another one for finding
bookings by email. That's all about
speed, not rules. So, what Code Rabbit
is suggesting is that we add one more
index that's unique, meaning the
database will enforce a rule that no one
can book the same event twice using the
same email. This means that the database
itself will automatically block
duplicates. So you can now very easily
just commit the suggestion by checking
whether it's okay. But of course
carefully review this code before
committing. It looks good to me. So I'll
go ahead and commit it and implement
this update. There's another major
suggestion. This time not a potential
issue but a refactor suggestion. And
right now our code is checking if
MongoDB URI exists as soon as the file
is imported. That means that if someone
imports this file anywhere, even if the
part of the app that doesn't actually
connect to the database, it'll
immediately throw an error if the
environment variable isn't set. So what
Code Rabbit is saying is that when you
throw errors at import time, it can
crash tools that just load your code
without needing to run the database,
like a build process or some tests. So
the fix is to simply move this check
inside of the connect to DB function
right before you actually connect. That
way we're only validating the
environment when the app is actually
trying to connect to MongoDB. In this
case, we don't get the autocommit
suggestion, but there is another thing
we get and that is prompt for an AI to
implement this feature. I'll go ahead
and copy it. Head back over to WebStorm.
Check out over to database
models branch
and run pool just to be safe. Now that
we're in here, I'll open up Juny and
I'll paste this command that Code Rabbit
gave me. And there we go. Very quickly,
it implemented it. So now if you head
over to MongoDB.ts,
you can see that it is checking for the
variable just here
within the connect to database function.
Perfect. We can now push this change by
running get add dot get commit-implement
code rabbit suggestion. Or you know
what? Why would we let code rabbit to
take the praise? Let's just say make a
check for the environment only if we
need to use it.
Perfect. And we can push it with that.
This suggestion from code rabbit will be
resolved which means that we are ready
to merge it. So just go ahead and click
merge pull request and then back within
our application. Check out back to main
run pull to make sure that we have all
the latest changes and then check out
over to and create a new branch for the
feature that we'll implement next. Now
that our database models are in place,
we are ready to create the API routes
needed to create the actual events
within the database. So call it API
routes. Exciting stuff coming up.
In this lesson, I'll teach you how to
create your first Nex.js API route. You
can think of API routes as a solution to
build public APIs with Nex.js,
essentially backend applications. So any
file you put inside of the API folder
will automatically be treated as an API
endpoint instead of a page. They are
serverside only bundles and won't
increase your client side. It looks and
feels something like this. API hello.ts.
We import some next API request and
responses from next define how our data
will look like. And just as you're
within your backend or Express
application, you can automatically
return responses from your API. That's
pretty cool. So, let's do it together.
Specifically within our application,
we'll use an API route to create events
and fetch them from the database. We
could use server actions for these, but
to give you a taste of all the NexJS
features, I'm going to show you how to
implement it using API routes.
So let's follow the documentation and
create our first API route within app
API.
Let's create another folder within it
called events. And within events, we can
create a new file called route.ts.
We can immediately start by exporting an
asynchronous function which is going to
be called just post as in a post
request. It'll accept a request of a
type next request and we can open up a
try and catch block. In the catch, I
will simply console that error that
might happen and I will also return a
next response. So this is how you
actually send data back from your API
through a next response.json JSON
command where we can say that the
message is something like event creation
failed.
Actual error will be set to now we can
check if error or E is an instance of
error
then we know that it'll have the message
attached to it. So I'll say error or E
do message. else we can say unknown. So
we're trying to be as precise as
possible when we can be. So if an error
is an instance of error then we render
an error message else we render unknown.
And now we are ready to get started with
the try part of the block. First things
first we need to connect to our
database. Thankfully we have already
created a function that does just that.
So I'll say await connect DB or you can
check out how your function is called
right here within your MongoDB.ts
file. Once we're collected, we want to
get access to the form data that we're
going to pass into this request by
saying const form data is equal to a
weight and we get access to it through
in express it will be rec.body. But here
in Nex.js it is rec.form data. Then we
want to parse that form data. So I'll
say let event and just define it like
this. Then I'll open up another small
try and catch block inside of which I'll
set the event to object dot from
entries. So we'll get all of the entries
such as key and value pairs from form
data and we'll grab them like this. That
way we're putting it into an object. But
if something goes wrong with that, we'll
return a next response.json
with a message of invalid
JSON
data format. And as the second
parameter, you can also attach
additional options such as a status of
400.
I also forgot to attach a status right
here.
This is going to be an unknown server
error. So something like 500. But if we
properly parse the form data from form
data into an object, then we can work
with it. So we can create the event in
the database by saying const created
event is equal to await event coming
from database. So we have to import it
by saying importvent
from add database slashevent domodel
and then we can create it by calling the
create method on it and we'll pass over
the event data.
Once we've created the event, we'll then
return the next response.json
JSON with a message of event created
successfully. And I'll pass in the
actual event data set to created event.
Here I have a typo. Thankfully, WebStorm
caught that for me so we can
automatically fix it. And I'll also
attach a status of a 2011 to it. 2011
stands for created. Believe it or not,
this is it. Your NexJS app joined the
modern times, came out of the closet and
transitioned from front end to full
stack. We can now make requests to our
Nex.js API. The easiest way to do that
would be through some kind of an API
client like Postman or in recent times
I've been using more of this HTTP. You
can head over to httpi.io
and simply download it. It's great for
making simple API requests. within it.
We can try to make the post request to
our application by selecting the type of
the request to be post and then typing
in 3000
slappi
slashevents.
Now we need to pass in some of the body
details in a form format. So here we
have to fill it in with all the right
data that our app expects. I'll just go
ahead and turn this into a text format
and paste it right here. I prepared an
example event that we can pass in. So
just so you don't have to type all of
this by hand, I'll also provide it to
you within the video kit down below so
you can copy and paste it right here.
Now, one thing I want to mention is that
we're using a pre-uploaded image right
here for simplicity. So if you simply
head over to this Unsplash URL, you'll
be able to see an image of an existing
conference that we haven't uploaded to
any kind of an image storage solution.
And that's not good. But don't worry,
we'll fix that soon. For now, I just
want to test whether we can actually
speak to our Nex.js API and whether it
can speak to our database and create
this new event for us. So, I'll go ahead
and click send. And it looks like we got
a 500. Something's definitely not right.
If we head back over to our terminal, we
might see some more info as to why this
error happened. And before I even open
it up, it seems like I know where it's
coming from. It cannot find this event
model and the terminal confirms it. So
are we importing it properly? It should
be coming from database event model ts.
We are generating a schema right here
and then from it we are exporting the
event. So it looks like I just missed a
forward slash. If we add it the path
will be correct and we can retry our
request. So let's make it one more time.
And this time we got a different error
message which I guess is good. Whenever
you get a different error message that
means that you're making progress. This
time we got an error directly from our
application indicating that our content
type was not a multiart form data rather
it was JSON. Now I decided to use form
data because we are also going to have
image uploads very soon and form data is
better suited for that. You cannot
upload an image through a JSON file.
Right? So, we have to switch this over
to form and then we'll have to add
fields one by one. Starting with the
title, we can copy some of the parts
that we had right here in the JSON form.
So, it's going to be cloud next 2026.
And then we have to switch it over to
here. Moving over, we have the
description. Actually, let's type out
all the key names so we can then add
values later on. We also have the
overview. Then there's the image.
There's also the venue, location, date,
time mode audience agenda organizer
and the tax. So now, let's slowly start
copying all of these values such as the
description
over from JSON to the form. And you can
do the same thing for all of the other
fields. I'll skip the part where I do
it. And there we go. I'm pasting the
last property. Finally, it's all here in
form data. So, let's go ahead and remove
this JSON body. And instead, we'll be
sending the form data as the request
body. Let's go ahead and send it. And
this time, we're getting stronger
validation. So, the error changed again.
And the mode must be either offline,
online, or hybrid.
In this case, I'll set it to just
hybrid. Finally, we got a 2011 created.
event created successfully and then we
got back the full event data coming from
the database.
You can know it is coming from the
database because it has the created at
and updated at fields as well as the
slug and most importantly the underscore
ID. If you truly want to verify that
this event got added to our database,
you can head over to MongoDB Atlas under
clusters, browse collections, and
there's going to be a test database with
an events collection name where you'll
be able to load your created event.
Congrats. You know what this means? It
means you successfully created a Nex.js
JS API endpoint that accepts the body
through form data, connects to and talks
with our database, creates an event, and
finally gives back the response. But as
you know, so far throughout this build,
I've introduced you to all the
production ready tools. And now we'll
bring this feature up another level to
allow us to upload and store our own
images and not just using the images
hosted on the web. So let's do that
next.
I promise that throughout this course
I'll try to use as many production ready
applications as possible to teach you
how to use them in your upcoming NexJS
applications. So I couldn't think of a
better one for image and file upload
than Cloudinary. So click the link down
in the description and sign up for free.
You can sign up with email or choose a
provider. and you might need to go over
the onboarding process. Once you
complete it, you'll be redirected to the
dashboard. And within the dashboard, you
can head over to the settings right here
at the bottom left of the screen. Then
head over to upload and finally add
upload preset.
You can name the preset anything you
like, such as Nex.js crash course, but
make sure to select unsigned as the
signing mode. This will allow you to
upload directly from the browser or the
embedded widget. You can also specify
where the uploaded pictures are going to
be stored. Right here, you can store
them within the events assets folder.
The rest of the settings can remain as
they are. So, just click save. Once
you've done that, head over to API keys
and copy this API environment variable.
Then, within your application, you can
head over to the env file and just paste
it.
You'll have to switch the template right
here with the actual API key and secret.
So go ahead and generate a new API key.
You'll have to enter the email
confirmation code and then you'll be
able to see it right here. I'll use this
new one. You can see I was using Cloudy
back in 2022. So I'll go ahead and use
this new API key. Modify the key right
here.
and then do the same thing for the
secret.
Now there's also another thing we need
to do to be able to load images coming
from cloudinary and that is to head over
into our next config and right here at
the top say images
define an object where we can define the
remote patterns of images and then
within an array add another object with
a protocol of HTTPS
and a host name. In this case, host name
will be res.cloudinary.com
because that's where the cloudinary
images are going to be stored. This will
just allow us to actually load them in.
Finally, within our app, you can just
run mpm install cloudinary. And now
we're ready to get started with
implementing the image upload feature.
Back within our route right here, API
events route. right here after we form
the general event data but before we
actually generate it we want to be able
to get the uploaded file. So I'll say
const file is equal to form data get
image that's where it's coming from and
I'll define it as a type of file.
Then if a file doesn't exist we can just
return next response.json
JSON with a message of something along
the lines of image
file is required and then we can also
give a status of 400. But if we do have
a file in that case we can convert that
file into a buffer. So I'll say const
array buffer is equal to await file dot
array buffer. This returns a promise
that'll contain a copy of the blob data.
Typically, when we work with files, we
want to get access to the blob. So, say
const buffer is equal to buffer from
array buffer. And now we can use that to
pass it over to cloud array and to
actually upload it. To do that I'll say
const upload result is equal to await
new promise where we get the callback
function with resolve and reject
functions. And here we want to use the
cloudinary object which we can import at
the top by saying import v2 as
cloudinary
coming from cloudinary which is the
package we just installed. I'll put it
at the top as that is an external
dependency. And then to upload it you
can type cloudinary.uploader.upload
stream
to it. You can pass an object with some
info such as the resource
type set to image as well as the folder
where you want to save it which I'll set
to dev event the name of our
application. Then as the second
parameter you can pass the callback
function of the upload. If it fails
it'll give us the error and if it
succeeds it'll give us the result. And
here we can open up a new function block
where we can say if there's an error we
can simply reject from this function
using the error itself and if it
succeeds we can simply resolve the
results. And then here you can say end
buffer. I pulled this directly from
cloudy docs. Now this upload result will
contain the URL to the image uploaded on
cloudenary servers. So we can say event
do image. So we're adding it to the
actual event data. This one right here.
And we can set it to upload result
dot seccure URL. Now TypeScript isn't
sure that upload result contains the
secure URL. So we have to wrap it in
parenthesis and define it as an object
that actually has a secure URL of a type
string attached to it. That way it'll
not complain.
And just like that we have added this
image to the event but this time the
real image that we uploaded and that
actually got hosted on cloudy servers
and we're passing it over to our
database. So it gets created.
Now if you still have HDPI opened with
all the fields that we sent before, we
can retry it, but now making a request
with an actual image that we're
uploading.
So instead of this image right here as a
text, we can pass over a new image
property, but now upload a file. So I'll
head over to our application and get the
event that seems the most fun. Let me go
with this one right here. I'll save this
image
to downloads
and then back over here I'll just upload
it. I selected it and you can see that
it is right here. So now we can try to
create a new event but this time with
the image that we have uploaded to the
form. I'll click send
and
this time it says event creation failed
duplicate key. Oh, that's good because
the slug is the same and it nicely
figured it out. This might actually be
that fix that Code Rabbit provided to
us. So, I'm super glad this happened. I
definitely want to change the name.
Maybe we're going to go with uh Cloud
Next 2027. So, let's go ahead and create
it.
And there we go. Event created
successfully. Cloud Next 2027.
This time if you take a look at the
image property you'll see that it is an
image hosted on cloudenry. So you can
copy the URL and if you search for it'll
actually open it up. So back on
cloudenary if you head over to assets
folders devvent you'll be able to see
this image that we just uploaded. Now
back within our application we want to
create a get request to fetch all the
events from the database. That's going
to be even simpler than creating this
post request. We can just collapse it
and create a new function just below by
saying export async function get.
This one will have a try and catch
block. In the catch, we'll do something
similar to before. We'll just return a
next response.json
JSON where the message will be set to
event fetching failed and we can also
display the error and finally I'll pass
over the status of 500 server error but
in the try we can actually try to fetch
this image by trying to connect to our
database first. So connect DB and then
we can get the events by saying await
event.find find and we can also sort it
by created at minus one which means that
the new events will be shown at the top
and now we can just return next response
thatJSON event list successfully or we
can say something like events fetched
successfully and then pass over the
events with a status of 200.
I told you it's going to be simple. This
is your second server API route. Now, we
can make that request within our HTTP by
switching this over to a get request and
then just heading over to the same
route. The HTTP verb is different, but
the endpoint is the same. So, if you
just retry the request, you'll see
events fetched successfully and then
you'll be able to see your two events
that we created. one containing the
image where we didn't have the file
upload implemented and the other one
with our own uploaded image. You can
also try to make a request within the
browser because by default browsers are
making get requests. So if you search
for localhost 3000
slapi
slashevents you'll see that you'll get
back the JSON data. This means that your
NextJS application is also acting as
right now very simple but with
possibility of becoming a very powerful
back-end API. But this isn't what you
want to display to the users. You want
to get the data in a nice format and
then display it on the front end. That's
the beauty of Nex.js applications.
Nex.js Next.js is essentially replacing
the need for a typical MER stack,
MongoDB, Express, React, and Node
because here, Nex.js handles all the
pieces. So, in the next lesson, let's
figure out how we can actually call this
API route from the front end side.
In this lesson, you'll learn how to use
the created API routes on front end and
then display the data you're getting
back from these API routes. Now, before
we call this API, let me show you one
thing that'll make it easier for us in
the future. Head over into your env file
and add a new variable. Next public
base URL.
Notice how this one starts with next
public and so do these two from post
hog. In Nex.js, GS the next public
prefix means that this environment
variable will be accessible on the
client side as well as the server side
whereas if you don't put the next public
here it'll only be accessible on server
side because it has to be a bit more
secure such as the MongoDB URI that has
everything to do with managing our
database or cloudinary URL for managing
our images but for some variables that
don't contain any very strict keys it is
okay for them to be available on the
client side. Maybe they have to be
executed on the client for them to be
able to work. So specifically, what is
this base URL? Well, for now, I'll set
it to http col/ slash localhost 3000.
And then later on, after we deploy the
application, this will be replaced by
the production URL.
Now that we have it, head over to our
page. And here we'll call the get API
route to get all the events created so
far. We can do that right here at the
top. And you can start by making this
page asynchronous. Yep. In newer
versions of Nex.js server pages can be
made completely asynchronous allowing
you to have top level await. So here you
can say a response and make it equal to
the await fetch of and now you can
either manually type localhost 3000 or
you can just use the base URL from
environment variables which will make
this code work right here right now but
also it'll work on production. Whereas
if you hardcoded localhost 3000 right
here this would break in production
because later on maybe you have
something like jsmastery.com/events.
So what you can do is get access to this
environment variable. I'll call it base
URL and we'll get it from
process.env.base
URL.
And here it'll start with base URL/
API slashevents.
Then we need to dstructure the actual
events from the data by saying events is
equal to await response.json.
Now we no longer have to import these
events coming from constants. Rather
we're already getting them right here
from our API. Now right here we're
mapping over them. But it looks like
this event doesn't know of which type it
is. So I'll say that this event is of a
type I event which stands for interface
event containing all this data of
exactly how this document looks like in
the database. And we can also add one
more check and that is to check whether
events exist and if they do exist
whether the events.length is greater
than zero. Only if those two conditions
are met then we can start mapping over
them. Now if you try to run your
application you'll see there's going to
be an error here saying that it cannot
find this base URL and that's because I
missed adding next public base URL in
order to be able to load it. That's the
real environment variable name. So if we
use that one, you'll see that then it'll
work or for you it won't work yet. It'll
say that it cannot load an image from
Unsplash. I already deleted it, so it's
working for me. But if you want to
immediately make it work for you as
well, head over to your MongoDB data
explorer under your cluster, find the
event that has the image of Unsplash and
then delete it entirely and just leave
the one that has the event with their
own uploaded image. If you do that, you
should be able to load the homepage
properly. In a similar way to creating
this get API route, you can also create
another API route to fetch a single
event by its slug or ID. And since SEO
is a priority for this project, a URL
something like forward
slashevent/nextgscom
2025 is much more userfriendly and much
better than a URL that looks something
like this with a random ID. Small
details like this significantly improve
discoverability. So when someone
searches for next config 2025, your site
has a better chance of ranking higher.
So we'll proceed with this slug approach
and build that route. But this time
since we have already built this get
route, I want you to think about how you
would implement the get route for
fetching a single event. Once you do the
thinking, we can let AI do the heavy
lifting. So to make it 100% clear, we
need a route that accepts a slug as
input and then it returns
the event details. And it can also
respond with an error message if the
event isn't found or if something goes
wrong. That's it. So now we can let AI
do the work while you focus on more
important aspects of your application.
That's how web development is going to
look like in the next decade. And you're
getting to learn that right here in each
one of my videos. So head back over to
Warp and then in the video kit down
below, you can find another copyable
snippet containing the AI command for
this feature. You're a back-end
developer working on an XJS app. This
time we need to create a get API route
under app API events slug route.ts. I'll
soon explain this naming structure that
we're using right here that returns
event details by slug. And then here I
was just a bit more precise to make sure
that both of us get the same input. So
now you can press enter and let it
generate this code for you. There we go.
It is done. So I'll go ahead and accept
it. That is perfect. Warp just
implemented this new dynamic API route
with complete slug validation
sanitization database querying using
mongus with lean method for better
performance for4 handling error handling
and type safe implementation. This is
exactly what we wanted. So back within
our application let's see this newly
created file under events slug route.ts.
Now, why are we using this naming
convention? Why did we have to create
another folder with square brackets and
then put this route file within it?
Well, that's because within square
brackets, you put dynamic parameters.
Our URL is going to look something like
this. Forward/events, which is static.
You can see that because it doesn't have
any square brackets. And then some kind
of a slug, not an ID like this, but
rather Nex.js gs conf 15, right? And
this part right here, you want to be
able to get within your application. So,
Nex.js will automatically take this data
and put it into a slug variable because
it is expecting one based off of this
folder name. So, now let's go through
this implemented route together. First,
we're defining some route params and
then we have this API event slug which
fetches a single event by its slug.
First we have a request of a type next
request and then right here we have the
route params which when dstructured
contain the slug. So as usual we're
first connecting to the database then
we're waiting and extracting the slug
from params. So one more time as a
second parameter to the get request
you're getting the route params you can
dstructure it and then you get whatever
the variable name is. If you named the
folder something like ID, then here you
would be able to dstructure the ID and
get it directly within your code. In
this case, we called it slug. So we get
a slug variable. We're doing some slug
validation to check whether it's
actually okay. So we're sanitizing it
like trimming it and lowercasing it so
we can make sure that it works nicely in
a URL. Again, this is just the extra
work that I maybe would not have done
immediately or would not have remembered
or would not have had the time to
implement it. this way, but this is
truly how it should be done. So, we
humans love to cut corners sometimes,
but AI doesn't do it if you instruct it
well. And finally, we're querying the
event by slug. So, we're saying event is
equal to await event.find one and we
find it by a slug. Then, if no event is
found, we simply return an error
message. But if it is found, then we
return the fetched error successfully.
Finally, in the catch part, we're
sending some errors like a 500 if we
cannot find the MongoDB instance or
another 500 if an unexpected error
happened. So now we have another API
request this time to get the details of
a specific event. And if you want to
learn how to build super secure
production-grade API routes complete
with robust validation and global error
handling with even custom logging to
figure out what is happening and what
could go wrong. Definitely check out the
ultimate NexJS course. You can see that
I'm just scrolling through how many
active lessons there are and through how
many API routes we're creating here.
We're diving deep into route handlers,
common API things to keep in mind, the
typical routes, as well as the dynamic
routes, and creating reusable API fetch
handlers. But with that said, we can now
easily fetch the details of one of these
events that we have right here based off
of its slug. So, we can test it in this
API client. I'll go ahead and copy the
slug cloud next 2027. At least that is
the case for me. So I'll head over to
API events cloud next 2027 and make a
get request and we indeed do get the
details of that event only. The next
step is to use this API route on the
frontend side of our application. So we
can make use of the content and display
it on the UI as it's shown right here
within Figma. We can do that by creating
a new file for this page. Since Nex.js
uses file-based routing, I'll create a
new folder and call it event.
And then within event, I'll create
another folder
with a dynamic route called slug. And
then within slug, I'll create a new
page.tsx.
Here we can run rafce.
And we're exporting this new event
details page. So, I'll rename it to
event details page. And now I'll put my
browser side by side with my editor
again so we can see what's happening.
Looks like for a long time I've had this
error saying fail to parse URL from
undefined API events. So, if you head
over to our homepage, that's where we're
seeing this event. It looks like base
URL is set to undefined.
But why is that the case if we are
setting it as the localhost 3000 right
here? Well, that's because I should have
used the full env next public base URL
like this. And then here we can use its
variable name. So if we do it like this,
you'll see that it'll work. But then we
have another error saying that images
unsplash is not configured under next
config. Remember that for one of those
events, we use an image that is already
hosted somewhere.
So what you can do is head over to next
config and add Unsplash right here as
another image host that we support. Or
you can just head over to your MongoDB
atlas under collections. Find the event
that has this image that we haven't
uploaded and delete it because from now
on all of our images are going to be
hosted on Cloudinary. So now if you get
back and reload, you'll see that now
we're fetching a real event from our
database and it's actually getting
displayed. So if you try clicking on it,
you'll be redirected to A44
because right now it is pointing over to
let's figure out where in the event card
it looks like the link is going to
events slug and here I called it event.
When it comes to the naming convention,
when you're naming API routes or
endpoints, it is always good for the
collection of the elements such as event
to be plural. So this right here is
correct, whereas the name of the folder
is incorrect. I'll rename it to contain
the plural form events so that a single
event within it is denoted by a slug. So
now if you click over on this event,
you'll be redirected to a new page which
is this event details page. Now, so far
this is just a static page that says
page. But how do we actually figure out
which event this is? So this is the
event with the following slug. Well, we
can do that by extracting it from the
params. I'll first turn this function
into an asynchronous function and then
dstructure the params and I'll specify
that the params are of a type promise.
specifically a promise that'll be
resolved with an object that contains a
key of slug of a type string. So now at
the top of your page you can simply say
const and then dstructure the slug from
the params but make sure to await it. To
be able to extract the value you have to
add the await keyword. Now you can say
return a section
with an ID of event
and then within it you can render an H1.
This time you can say event details of
an event with the following slug. There
we go. Event details cloud next 2027.
This means that this page right here is
a dynamic page loading this slug from
the URL. But now we have to make a get
request to this new API route we created
which is under API events slug route
that'll fetch all the necessary details
so we can actually render it and display
it properly on that details page. So
let's call it by saying const request is
equal to await fetch. We can once again
get that base URL at the top but not
make the same mistake that we made
before. I'll say base URL and make it
equal to process.env
and it'll be next public base URL. So we
can make a fetch request to base URL
slash API
slashevents
slash slug.
Then from here we should be able to
extract the data
and that's going to be equal to await
request.json. JSON. If data is not
found, we can simply return a not found
method which is given to us by next
navigation. So, we're automatically
redirecting to a 404. And that seems to
be the case. If we click on it, we're
automatically redirected. So, why isn't
this fetch request giving us the actual
data? Well, if we head over into this
events slug route, you'll see that we
are returning from it right here as
message and then the event. So event is
the name of the data, not data. So we
can dstructure event
like this.
If you do it like that, you'll see that
now you won't be redirected because now
the event data is actually there.
Perfect. This was a good one. In the
next lesson, let's make this page go
from this to this. We'll fetch the full
title, summary, and overview, as well as
the event details and the complete
agenda, some info about the organizer,
and the tax. We can also render a form
to book it. So, let's do that next.
In this lesson, we'll implement the UI
of the event details page and display
the data that we've been working. so
hard to get from the API. Looking at the
Figma design, the event details page
will need different elements from these
little pills for the event tags, front
end, backend, AI, open source, and so
on. The event signup card and for the
similar events, we can reuse the event
card from the homepage. So, let's
develop it. I'll start with a header by
creating a new div that'll have a class
name equal to header. Within it, we can
display an H1
that says event description.
And just below it, another P tag that'll
display the event description like this.
Event description, Google's premier
cloud computing event. Now, if you don't
want a repeat event every now and then,
then we can immediately dstructure all
the data from that event such as the
description.
And what else do we have on it? We also
have the image, the overview,
the date,
time, location, mode, agenda. Let's
remember what else do we have in there.
It's the audience that is a good fit for
this event. And finally the tags.
So now we're dstructuring all of this.
So we have to check whether the event
that um well let's say description for
example exists. And now it's going to be
just description.
So if there's no description that must
mean that there's no event as well. And
we can just display the description.
Perfect. I'll give this p tag a class
name of margin top of two to divide it a
bit from the title. And then below this
div, we can create another div that'll
have the class name of details. We will
split this div into two parts. One will
be for the left side which is the event
content
and then the right side will be for the
booking form. So I'll call it booking
form. For now, for the right side, we
can just say aside
with a class name of booking.
And then we can display a P tag that
says book event.
That'll have a class name of text-LG
and font- semibold.
For now, on mobile devices, it appears
right here on the left side. But if you
add something here on the left side like
a div
that has a class name equal to content
and within it you display an image with
a source of let's do image and an al tag
of event banner that has a width of
about 800 as well as a height of 800 and
a class name equal to banner.
If you do it like that, you'll see this
big image appear. And then later on,
we'll make this book event appear on the
right side so it fits the design. But
for now, let's focus on the main
content. So below this image, I'll
display another section.
This section will have a class name set
to flex-ol-gap
2. to create some spacing in between the
elements of this column. I'll create an
H2 within it that'll say overview. And
then below this H2, I'll create a P tag
that'll simply render the overview we're
getting from the database. Then below
this section, we can create another
section. It'll also have a flex gap of
two. And this time we'll render the
details. So I'll say H2 event details.
And now here we'll have to render all of
these different little icons. One will
display the date, another one the time,
and so on. So we'll create this as a
reusable component that we can easily
call.
We can do that by creating a new small
component right on top of this one.
And I'll call it event detail item.
It'll be a React functional component
with an immediate return. As the props,
we'll pass in the icon, the al tag, and
the label for the icon. We also need to
define the types of these three props.
All of them will be strings. So I'll say
icon is a string, alt is a string, and
the label is a string as well. And then
for each one we can just return a div
within which we'll render the image that
will display the icon the al tag with a
width of about 17 pixels height also 17
and then below it we can display a p tag
that'll render the label. So now we can
use this new component right here below
the event details. I'll use it by simply
calling this reusable component of event
detail item and I'll pass the write icon
to it. So that's going to be forward
slashicons/cal.
SVG with an al tag of calendar and a
label of it's going to be the date. So
now if you save it, you can see how it
looks like right here. We also want to
attach some class names to this div
wrapping this image and the P tag. And
I'll give it a class name of flex-
row-gap
2 as well as items center. When you add
these class names, it'll look much
better. Just like this. And I don't
think I even need this margin top right
now. Perfect. This is good. So now if
you scroll down, we can now duplicate
this event detail item a couple of times
for the time, the location, mode, and
the audience.
I think that's four more times. The
second one I will rename to clock and
it'll actually render the time. Then the
next one I will rename to pin as in
location and this one will render the
location. After that we have the mode.
So I will render this one and call it
mode. The label will be mode as well.
And finally for the last one I'll go
with audience.
So now if you save it very quickly we
get all of these different icons. Of
course, if we properly spell clock,
it'll look even better. Now, below this
section, we can render the event agenda.
For the agenda, I'll also create a new
reusable component right at the top
by saying const event agenda,
which will accept an array of agenda
items.
So I'll define the type of the agenda
items
to be an array of strings.
And then we can automatically return a
div that'll have a class name equal to
agenda.
It'll have an H2 that says well agenda
and then a ul, an unordered list which
will map over all of the agenda items.
And for each one, it'll return a list
item
that has a key equal to item. And it'll
simply display what that item says. So
let's fix this spelling right here,
event agenda. And let's just render it
as a component right here below below
the section. I'll render a self-closing
event agenda component. And under agenda
items.
I'll pass over JSON.parse. We have to
parse the data as it's coming
stringified. We'll pass over the agenda
and then the first property from it. And
now you can see exactly what is
happening throughout this event. Now
below that we'll render some information
about the organizer. So I'll render a
section with a class name of flex- call
gap 2.
It'll have an H2 that'll say about the
organizer.
And then below it, we'll render a P tag
that'll say organizer. Let's make sure
that we have dstructured the organizer
right here from the event. It doesn't
look like we did. So, I'll do it right
now. And now we'll be able to see more
info about Google Cloud.
Perfect. Finally, we have to render the
tags right here below. So for that I'll
also create another reusable component
that we can easily call const event tags
which will accept the tags which is
going to be just an array of strings
and we can immediately return a div with
a class name of flex flex- row a gap of
1.5
and flex wrap. So they wrap if they
cannot fit in one line on smaller
devices. Then we'll map over the tags
and for each tag I'll return a div
that'll have a class name equal to pill
and a key of tag and it'll display the
tag. So now we can collapse it and we
can call these event tags right here
below the section where we talk about
the organizer
by simply rendering event tags
to it as the tags. I'll pass over
JSON.parse
tags zero to get the first part of the
tags and we'll just render it. and you
have cloud, DevOps, Kubernetes, and AI,
which are specific tags for this
specific event. Now, if you expand this
UI, you'll see that it looks good. It
looks great actually on desktop, but
there's something missing. This book
event functionality on the right side is
pretty empty. And that's where all this
light is shining anyway. And of course,
the main point of the app is to be able
to book an event. But before we go ahead
and implement that feature, I want to
make sure that what we have developed so
far doesn't only look good, but actually
doesn't break and follows clean coding
practices. I don't want to open up a PR
just yet, but thankfully we can quickly
check it using Code Rabbit CLI. You can
click the Code Rabbit CLI link down in
the description and you can install it
with a single command. You copy it and
you paste it into your terminal. Once
that is done, you can restart your shell
or simply run source zrc or whatever
your command says. And then after that,
you can run code rabbit o login to
authenticate. This will authenticate you
with your account. So you can copy the
token and then paste it into your
terminal. Now that I've authenticated, I
will expand my terminal and give it some
more room. And I will simply run code
rabbit. This will open it up right here.
And to dive into the review, you just
have to press enter. And now we're
channeling the spirit of senior
developers. And the AI is pretending
that it knows better than you. So let's
see what it actually comes up with as
it's analyzing your code. You can keep
reading these uh messages. Pretty cool
way to pass some time as it's analyzing
the code. Given how it's messing with
us, it better be good. Thankfully, it
doesn't look like we have any code
issues with our event details page. Just
some grammatical errors in the comment
and error messages. It's pretty cool how
we didn't even write this comment right
here. It was written by AI and it looks
like AI messed up on the grammar right
here. So, it's suggesting us to fix it.
We can fix it very easily by using an AI
prompt or we can make the change
ourselves. But as I was reviewing this,
it also provided some additional changes
that we can make such as validate the
base URL to prevent the runtime errors
or add some additional error handling
for our API fetch. This is actually a
great suggestion. Oh, looks like we do
have some potential issues on the events
details page as well. One is validating
the base URL. some more to add error
handling to JSON parse to prevent
crashes. And then adding some
comprehensive error handling for data
fetching. These are all gray suggestions
which you can now apply with a single
key press. It says A to apply the
suggestion. So if I press the letter A
and then head back over here, you can
notice that it automatically applied the
changes right here. So when we're
fetching the event, it's actually adding
this try and catch block to make sure
that everything goes right. And if it
doesn't, it'll properly fetch it and let
us know. So feel free to apply as many
of these as you want. For now, I'll just
exit it. Back within our event details
page, we still have to do the right
side, which is the booking form. So
let's go ahead and create a new
component for it right here within the
components folder. Create a new file and
call it book event.tsx.
run rafce to quickly spin it up. And
then back over within this component,
instead of this p tag, I will render a
div that has a class name set to signup
card. Within it, it'll render an h2 that
says book your spot. And then we'll
check if anybody else booked this event
before us. We can do that for now by
creating a new variable right here const
bookings and I'll set it to 10.
And then we can say if bookings is
greater than zero in that case we can
render a p tag
with a class name of text-sm
and we'll say join bookings
people who have already booked their
spot. else if booking is equal to zero.
In that case, we'll render a P tag with
a class name of text-s
and we'll simply say be the first
to book your spot.
And then below it, we want to render the
book event form. So, if you do this and
collapse your IDE a bit, you should be
able to see it here. book event and here
it is. Of course, on larger devices, it
is actually appearing on the top right
side. So, I think we can even keep
developing it like this enlarged just to
the point where we can still see it on
the screen and we can develop that
component. Let's start with a div that
has an ID set to book event and we want
to figure out whether this current user
has already submitted their email to
join this event. So we'll need to use
two different use state fields. Uh the
first one will be their email of course
which at the start will be equal to an
empty string and then the second one
will be if they have submitted their
email already which will at the start be
set to false. Now since we're using
hooks we automatically need to turn this
into a client component. So at the top
I'll add use client and you can see that
it works. And now if we have already
submitted. So if submitted is true we
can just render a p tag with a class
name of text-sm
and say thank you for signing up. But if
we haven't submitted already, we then
want to render a form within which we'll
render a div within which there's going
to be a label with an HTML for the email
field and it'll say email address
and below it I'll render an input
with a type of email
a value of email coming from this state
on change we will simply call e set
email e target value.
I'll put all of these into new lines so
you can see them better. And then we'll
also give it an ID set to email as well
as a placeholder of enter your email
address. And now we can see this input
appear right below.
Finally, below it, below this div
containing both the label and the input,
we can render a button with a type
submit and a class name equal to button
dashsubmit.
Within it, we can simply say submit.
That'll now look something like this.
Perfect. Now, what are we actually
submitting? Well, we want to create a
new callback function called handle
submit that'll take in the event of a
type react dot form event and it'll do
something. We'll actually call this
function on once this form is submitted.
So, we want to say onsubmit call handle
sububmit right here on this form. Then
once we're handling the submit, we can
simply run e.prevent prevent default to
prevent the default behavior of the
browser to reload. In React and NextJS
applications, you don't want to have
that reload happening. And then what
else can we do? Well, for now, I'll set
the timeout and then within it a
callback function for about 1 second
where I'll set the submitted to true. So
once we submit it, like let's say I'm
joining with contactjsmastery.com
and I click submit, it'll say thank you
for signing up. Later on, we can hook
this up to the actual functionality of
booking this event to actually be able
to join and participate. But with that
in mind, amazing job for developing this
event details page where we can see all
the information about this event. And
not only are we rendering the UI, but
you've also done a great job of fetching
this dynamic parameter of slug. And then
based off of that slug, you're now
fetching that event directly from our
API right here within API events slug
route.ts
which returns the details of this
specific event. Great work. And in the
next lesson, we'll learn more about
server actions. They're like API route's
younger brother. A bit smaller and
faster, but still super powerful. So,
let's dive into that next.
Up to this point, you've learned how to
create and fetch the API routes from the
front end. But there's another powerful
NexJS features that we haven't talked
about yet. It's the server actions.
So, what is a server action anyway?
They're simple functions that are
asynchronous and are executed on the
server. You can trigger them directly
from the client side, for example,
during a form submission to securely
handle mutations, side effects, or any
other serverside logic without exposing
sensitive code. Essentially, they allow
you to make a post request straight to
the server minus all the boilerplate
code that you typically would have to
write when writing API routes. The only
thing you have to do to make something a
server action is to add the use server
directive.
You can either add it at the top of the
function or at the top of the file that
then uses multiple server actions. So
let's create our first server action and
see how it's different from the API
routes we've created so far.
Head over into your lib folder right
here and then create another folder
within it called actions.
Within actions, create a file for your
first action and call it
event.actions.ds.
Here we'll implement a function that
returns similar events by a slug. So,
first things first, as we discussed, you
have to add a use server directive at
the top. That means that all the code
within this file will be executed on the
server, not exposing any important info.
Then we can export and create a new
function. get similar events by slug.
It'll be an asynchronous function that
accepts a slug of a type string and it
simply opens up a function block. I'll
open up a try and catch block. In the
catch, I'll simply return an empty array
because we weren't able to fetch any
similar events. But in the try, I'll
first try to connect to the database,
same as what we've done within our API
endpoint. And then I'll simply try to
fetch all the events by saying const
event is equal to await event.find
one. And we'll find it based off of a
slug. But first we have to import event
right here at the top by saying
importvent
coming from add/ database/event.model.
And then once we find the event we're
looking for, we can find all the similar
events for that event by making another
await event.find call. But this time
we'll say underscore ID must not be
equal to the ID of the event we found
above. So we don't want to show a
similar event that is the same as the
event we're showing similar events for.
So that's the first thing. And the
second thing is how are we going to know
which events are similar? Well, by tags.
So I'll say if dollar sign in as an
included
specific event tags. So if this event
that we're searching for includes any
tags that the original event has then
they must be similar. And finally we'll
return all the similar events. You can
see that WebStorm even tells me that
this local variable is redundant because
we can immediately just return these
similar events and that allows us to
make it even simpler. So let me ask you
just how simple and straightforward is
this? You can focus purely on the core
business logic of your application. In
this case, getting similar events by
slug without having to worry about
requests, responses or any extra setup.
It feels just like writing plain
JavaScript but on the server side.
Remember when you created an API route
to create an event that is right here
under API route.ts? Well, first of all,
you needed a new file following this
file naming convention for API
endpoints. Then you had to define this
special post function that has this
special request and responses. And then
you had to use this next response to be
able to send back the data. I mean, it's
not that difficult and AI helped us with
validations and error handling, but
there was still a lot of boilerplate
like defining the route and using the
next responses.json JSON to send over
the data. And then not only that, when
you actually called some of these
routes, you then have to make a fetch
request to be able to load in the data
from that API. But with server actions,
all that goes away. You just write a
regular JavaScript function with your
serverside logic and call it anywhere in
the app, just like calling any other
function. Too good to be true, right?
But let me show you. We can now use the
server action within our event details
page. So head over to event slug page
and then right at the bottom of this
page I'll say const similar events which
is going to be of a type Ivent array. So
it's going to be an array of events is
equal to await
get similar events by slug and we'll
pass in the slug. It cannot be any
simpler. No need to even make a fetch
request. And just like that, right below
at the bottom of this page where we have
this div and aside and then another div
below all of it, still within the
section, I'll create a div with a class
name equal to flex wful flex- call gap
of four in between these events and a
padding top of 20 to give it some space.
I'll render an H2 that'll say similar
events. And within it, I'll render a div
with a class name of events. And
finally, we can now check whether
similar events.length
is greater than zero. And if it is, we
can then map over similar events by
saying similar events.m map. And then
for each similar event
of a type I event,
we can automatically return our reusable
event card that we created before. And
then you can pass all the right
properties. In this case, I think we can
even spread them out by saying dot dot
dot similar event like this. And don't
forget to give it a key because we're
mapping over this element. So that's
going to be similar event dot id. Now, I
promised this would work, right? So, if
you scroll down, we're expecting to see
some similar events, but there are none.
That's because our tags aren't stored in
the right format. We're using form data,
remember? Which means that the array
that is being passed over is serialized
as a string. So, currently our tags in a
format that looks like this, a string
containing an array containing different
tags, which is why MongoDB stores it
incorrectly. To fix this, you can head
over into our API events route.ts. Head
over to where we're first creating a new
event. Right before we upload the file,
we can get access to the tags by saying
tags are equal to JSON.parse form
data.get
tags
as string. And tags has to be a string
as well because that's the property
we're getting from the form data. In the
similar way, we can get the agenda
because it's also going to be within a
stringified array. So get the agenda by
saying form data.get agenda. Now just
before we create a new event with this
information detail, we're not going to
pass in just the event. We're going to
pass in the spreadit properties of the
event. So containing everything but with
stringified tags and agenda. But then
we're going to override the tags with
the new tags and the agenda with the new
JSON parsed agenda. So this way we're
storing it in the right format. Now if
you head back over to your API client
and you head over to the post request to
API events, we need to create at least
two new events having the same tag. And
then that way we'll be able to see
similar events. So what I'll do is I'll
change the name to something like the
real cloud next 2026.
This one will have the tags and the
agenda in the right format. And I'll
upload a different image. You can get
the images directly from our code by
heading over to public images. And then
you can see event 1 2 3 4. You can then
open that up in Finder or File Explorer.
If you're in Windows, you can delete
this image right here and add a new one
by simply drag and dropping the other
event. Now, I'll create this event. I'll
change the name to something like the
Real Cloud Next 2028,
which should be related to this one
because it has similar tags. And I'll
upload another event image,
and create it. Now, if you head back
over to Devvent, you'll be able to see
two new events. Oh, looks like we have
some kind of a bullet point right here
which we have to fix. So, let's do that
right away. That should be within our
reusable event card. So, head over into
components event card. And it doesn't
look like it's here. But if we head over
to where we're displaying those reusable
cards, that's going to be within the
homepage. We have an LI here and the ul.
We can give it a class name of list none
which will remove those list item dots
right here above each one of these
cards. Now you can see two new events.
The real events are the ones with fixed
agendas and tags. So if you click on it,
you'll see that as you try to open it,
it'll now break because we're trying to
parse what is already an object. So this
JSON parse was just a quick fix until we
get the data in the right format. So
back within the details page, we'll have
to stop parsing the agenda and the tags
and just display the tag and the data
itself because it's already coming in
the right format. So you can say agenda
and tags without getting the first
element. If you do that, you'll notice
that it'll now work even though this
image is a bit too low res for what
we're trying to do here, but you get the
idea. We can upload more quality images
and now it's rendering the tags. So if
you check out similar events for the
real cloud next 2026, we should be able
to see it here, but it's still not
popping up. So let's try to console log
the similar events right here to see
what we're getting back. You'll get this
console log within the terminal because
this page is rendered on the server
side. And we can see that similar events
is an empty array. That means that we
have to head back over to get similar
events by slug and check whether our
query is correct. And it looks like I
made a typo right here. We're first
searching for an event whose ID is not
equal to this current event ID. But then
we need to close this part right here
and do another check for the tags. These
two have to be separated. With that, our
similar events should now actually bring
back another similar event with the same
tag. So if you get back, you'll be able
to see it on the screen. But there are
some warnings such as that we're passing
an empty image. might be due to how
we're spreading the data into the
similar events. So, we're spreading the
full event data containing the image and
all the other properties. And here we're
automatically dstructuring them. And I'm
actually super glad this error popped up
because it's something that is super
hard to pinpoint exactly why it's wrong
because it feels super right. like we're
just passing over the event, spreading
all of its properties. And these
properties indeed contain the title,
image, and everything else that it needs
to render it properly. Similar to what
we're doing on the homepage, right?
Nothing is different. We're simply
spreading the event of the type I event.
But then why is the event card saying
that this image right here is undefined
and all of the other properties for that
matter? Well, that's because we're
trying to render objects or what
seemingly are objects returned to us by
mongoose. These are actually mongoose
documents, not regular plain old
JavaScript objects. So, when you try to
spread them, they behave a bit
differently. They look like plain
objects, but are not exactly the same.
So, you need to ensure that you're
returning the plain JavaScript object
from your server function. You can do
that by simply adding the lean at the
end of the method. That way when you do
and when you try to pass over and spread
this object, it'll spread properly and
we can pass in the title as the unique
identifier. So now if you get back,
you'll see one similar event. And if you
click on it, it'll lead you to the
details page of that event. So with
that, our app got so much more
functionalities because you can now
actually traverse between different
events that you like. Perfect. In the
next lesson, we'll dive into caching.
As your application grows, one of the
biggest performance wins you can achieve
is through caching. And in NexJS16,
that's easier than ever thanks to the
new use cache API. On the NexJS16 update
document page, if you search for cache,
you'll see that it's mentioned 40 times.
Yep, there is so many new stuff
regarding cache. So today we'll go over
this new use cache directive which can
now be used to cache pages, components,
functions, and which leverages the
compiler to automatically generate cache
keys wherever it's used. What this means
for you is that whether it's database
queries, API responses, components, or
very expensive calculations, use cache
will now let you cache results during
build time or runtime for a certain
customized time. improving speed,
reducing load, and keeping your app
lightning fast. While NexJS16 was still
in beta, I spent time preparing this
little dev event application so we can
showcase how caching functionalities
work within it. Even though use cache
isn't in its experimental stage, you
still have to manually enable it within
next config. So, whichever next.js JS
application you're working on. Head over
into your next config and right at the
top say cache components and turn this
to true. If you hover over it, it'll
tell you that when enabled in
development and build, Nex.js will
automatically cache page level
components and functions for faster
builds and rendering. This includes the
partial pre-rendering support. And for
more info, you can refer to the cache
components documentation. That's right
here in the latest version of Nex.js16.
The cache component flag is a feature in
Nex.js that causes data fetching
operations in the app router to be
excluded from pre-renders unless they
are explicitly cached. This can be
useful for optimizing the performance of
dynamic data fetching in server
components. It is super useful for when
your application requires fresh data
fetching during runtime rather than
serving from a pre-rendered cache. So
what this allows you to do is when you
turn on cache components, they can be
used together with use cache so that
your data fetching happens at runtime by
default unless you define specific parts
of your application to be cached with
use cache at page, function or component
level. Finally, we can make the decision
ourselves. So, now that we've enabled
it, we can use it to cache a whole file,
a component, or even just a function
result or a network request. Now,
depending on your app's functionalities,
you might want to turn it on or off
within a couple of different components
or pages, but I'll show you what we can
do within this little demo application.
Within the app folder, there's our
homepage. And at the top we are fetching
some data from our API route. Now this
is a super common use case. You're
fetching some data and displaying it.
Now let's try to cache the result of
this API. For example, we can cach it
for an hour. The only thing you have to
do is turn on the use cache directive
right here at the top and then say cache
life. Similar to cache control with a
max age. It's a custom time span for how
long you want to revalidate the data.
You can import it from next cache and
simply say hours. And you can see that
this is one of the default options. You
can provide a full config such as stale,
revalidated, expire. Or you can just say
seconds minutes hours days weeks or
max. If you head over to another new
feature under functions, there's a cache
tag. And the cache tag function allows
you to tag cache data for ondemand
invalidation. By associating tags with
cache entries, you can selectively purge
or revalidate specific cache entries
without affecting other cache data.
Finally, this is the level of
customizability with caching that we've
been waiting for from Nex.js for a long
time. So first of all to use the cache
tag feature you have to enable the cache
components flag in your next config
which is what we've just done and then
you can call cache tag which takes one
or more string values such as my data in
this case. You can then purge the cache
on demand using revalidate tag API in
another function for example from a
route handler or a server action which
will give you the latest updated data if
you need it. A couple of things that are
good to know here arempetent tags which
means applying the same tag multiple
times has no additional effect. No need
to do that. You can assign multiple tags
to a single cache entry by passing
multiple string values to cache tag and
there are a couple of limits. The max
length is 256 characters and the max tag
items is 128. So here are a couple of
examples. For example, tagging
components or functions. In this case,
we have the bookings data which we can
see right here. You can also use the
data returned from an async function to
tag the cache entry. So for example,
here we're getting the bookings data and
we can cache it just before returning
it. Finally, when you need new data, you
simply call revalidate tag right here.
We don't have to do absolutely anything
because this component will
automatically be cached for hours. And
don't forget to open up this component
block right here. Now, taking a look at
our homepage, it looks like we have
three posts right here. So, if we try to
create another one, let's say we want to
add an event for 2029. I'll upload some
different image and click send. Now, if
you reload on our homepage, you'll see
that a new event won't be shown, which
might seem a bit counterintuitive at
first, but that actually means that our
caching is working. If you were to check
on MongoDB Atlas, you would be able to
see this new event there.
But even though it is in your database,
your network request has been cached for
the next hour. That means that you'll
still be seeing the stale data, which
for the events application where there
are no new events happening every couple
of seconds is totally okay and actually
saves us so much time on traffic and
makes the page load faster for all the
other users. Of course, what you want to
cache, where you want to cache it, and
how long you want to cache it for
depends on your application's needs.
This was just a little demo to show you
how use cache works. So, if you want to
learn use cache in depth and understand
how it works on all kinds of levels and
how we can integrate it within our dev
overflow application, I'll cover that
soon in a dedicated module of the
ultimate nex.js course. Now that we've
implemented caching, I think it might be
a good time to open up a PR to review
all of our changes and everything we've
done so far. Starting all the way from
implementing API routes. So I'll run git
add dot git commit-m
implement API routes server actions and
caching and the event details page. I'll
run git push to push it over to this
branch. But of course, we have to set
upstream to push it over to our remote
repo. Then on GitHub, you'll see that
your API routes branch has recent
pushes. So go ahead and create a new
pull request. There's 12 files changed
and I think some of them were pretty
lengthy. Everything for creating the
routes to get the event details to
creating the general routes for creating
new events as well as creating the pages
to display them. Let's give Code Rabbit
some time to review it properly. And
here's the walkthrough. In this PR, we
introduced event management capabilities
including API endpoints for creating and
fetching events, a dynamic event details
page with data fetching and related
events discovery, a booking form, image
upload integration with Cloudinary, and
serverside caching configuration. Here
we have a more detailed per file or per
cohort of files changes. Some of these
are API event routes. Some are
asynchronous pages that fetch the data
from those APIs. And then we also have a
server action that gets the similar
events by slug. Here's a more detailed
sequence diagram than what we've had
before. A user requests the event
details page. Then we fetch the event by
slug from the API. And then the API
talks to the database by quering the
event by slug. Once the event is
returned, we simply bring it back to the
user. Now on the event details page, we
have a separate server action that
fetches the similar events. We find them
by tags and then finally return them
over to the user. There are two
additional diagrams before. This one is
interesting and is all about creating
events. So from the admin side or in
this case from our HTTP client, we can
make a post request to the API events,
parse the data from the form and then
upload the image to the dev event folder
on cloudinary. We get back the image URL
and finally create the event with image
URL tags and agenda within the database.
Finally, our server responds with a 2011
and we have our event created. So this
PR would take about 20 minutes to
review. It introduces multiple new API
endpoints and pages. But the logic
within each is straightforward and
follows consistent patterns. This is
very important not only for AI reviewing
your code but also for people that are
checking it and for ourselves later on
maybe in a couple of years once we need
to add some additional features. It's
good that the code is simple to
understand. Now, in this case, Code
Rabbit found nine nitpicky comments on
how we can improve our code. Some of
these are very simple grammar fixes, but
also it found some real and tangible
changes we can make to our code, such as
right here. An additional piece of
functionality that we can add to our
application is to actually authenticate
and authorize the users to be able to
access this post request which fixes a
big security risk and that is that
absolutely everyone is able to create
events. So naturally some users might
spam it or post some malicious content.
Adding authentication to this
application is definitely the next
meaningful step forward. Feel free to
review the other updates that code
rabbit suggests.
And then once you're done, you can go
ahead and merge it.
Once it is merged, back within your
application, you can run gitpool to pull
the latest changes and then navigate
over to main by running git checkout
main and pull the latest changes on the
main branch one more time. Now that our
application is looking great and look at
that, my new event also showed up
proving that the caching actually works.
we can now start getting a better idea
of how users are using our app. And
we'll do that by integrating a couple
more Post Hog features. So, let's do
that next.
Remember our Post Hog dashboard from
earlier? Looks like we're already
starting to get some users. And you can
see that this initial dashboard already
has some very interesting insights such
as the page viewfunnel by browser. So
you know how many people enter, drop
off, and return. But to be able to
extract more useful insights on top of
just daily active users, there are some
changes we have to make within our code
to then be able to extract those
insights. One example that comes to mind
is within the book event form. This is
the place where we actually make a
connection from our user or specifically
this user's email to the event itself.
So for that we'll quickly create a new
server action that we can use to modify
our database. Within lib actions create
a new action called booking.actions.ts.
And within it you can export a new
asynchronous function called create
booking which will accept uh the three
things that it needs. The event ID so we
know how to update it in the database.
the slug as well as the email of the
user that wants to sign up for that
event. All three of these will be of a
type string. So we can just define that
right here so TypeScript doesn't
complain. That's going to be the event
ID of a type string. Slug of a type
string as well. And finally the email of
a type string. And now we can open up
this function block. You already know
that server actions are quite simple.
We'll open up a try and catch block. In
the catch, we will simply console.
Saying that create booking failed and
something went wrong. And we can return
a success set to false and also the
error set to error. But if we try to
succeed, we can first connect to the
database by saying await db connect and
then make a booking
by making it equal to await
booking.create.
So we're creating a new instance of a
booking. First things first, make the
whole create booking server action
asynchronous. That's important. and also
define this file as a server action
files by using the use server directive
at the top. Then we need to import
booking right here at the top by saying
import booking from add/ database
slashbooking
model. Then we can create the booking
with the information that we have right
here. The event ID, the slug and the
email of the user who is trying to book
it. Finally, we can return success set
to true and then pass over the data of
the booking. And you can either
stringify it before passing it over or
you can add the lean method right here,
which will return it in a basic
JavaScript format, not as a MongoDB
document. But to be able to call the
lean method on it, you actually have to
first wait for the result of that
function, which means that you have to
wrap this await within additional
parenthesis. So we can then call the
lean method on it. Now we can head back
over here within our handle submit and
we can call it. I'll say const
dstructure nothing for now and just call
await create booking. Since we're using
a weight, we have to turn on async right
here. And we'll pass in all the
necessary props such as the event ID,
the slug, and the email of our user. But
right now, we don't have access to the
event ID or the slug within this book
event form. So, how do we get access to
it here? You could opt in for some kind
of a global state management solution,
or you can just see where the book event
is getting called. In this case, it is
right here within our details page. And
then to it, we can just pass those two
additional props such as the event ID
equal to event do ID as well as the slug
equal to event. slug. We just have to be
careful to make sure that the event
actually has the ID or we might need to
use the underscore ID to make this work.
Then you can dstructure both the event
ID and the slug. And these are going to
be of a type event ID is of a type
string. And slug is also of a type
string. Now we're getting them through
props. And you can see that they're
properly getting passed right here.
Event ID and the slug. And as the result
of the create booking after we resolve
the promise, we're going to get either
the success or the error depending on
how it does. So we can open up an if
statement and say if success we'll set
the submitted to true and else we will
simply console log the error by saying
console. Booking creation failed and
then we can render the actual error
we're getting back from this function.
Oh, and we have to fix it. I think we
called it e but we can actually call it
error. Great. So now we're no longer
just setting the timeout and preventing
the default. We actually want to submit
this form and we're doing it with this
simple server action. But now that
brings me to the part where we want to
track the number of submissions that
we're getting through our application
and maybe track the behavior or the type
of the users that are more likely to
create that event. We'll do that through
post hog. Right after we submit, I want
to capture an additional Posthog event
by importing Post Hog right here from
Posthog.js. JS and then using the dot
capture method
which I'll call event booked then you
can pass some additional parameters to
it such as the event ID the slug as well
as the email and likewise if something
goes wrong we can also capture something
but this time it won't be an event
rather it'll be an exception so I'll say
capture exception and I'll pass in the
error now if you head back over to
application. Head over to one of the
latest events and if you enter your
email address and click submit. And
actually I think this error was here
even before we submitted the form. See
when we turned on the cached components
due to the change in how Nex.js handles
PPR partial pre-rendering. Now every
component either has to be dynamic or
cached. And if it is dynamic, it has to
be wrapped with suspenses, which is
exactly what this error says. It says
route event slug referring to the
details page. A component access data,
headers, params, search params. In this
case, it is params getting the slug from
the URL or short-lived cache without a
suspense boundary nor a use cache above
it. So this documentation page talks a
bit more about it, but the simplest fix
right now is to head over into that
event details page and add the use
cache. So you can now simply say use
cache and then below it you can use the
cache life which you can import from
next cache and you can make it ours same
as it is on the homepage. If you do
this, it'll no longer complain because
now when we submit the data or when we
just visit the details page, it'll now
work because it's getting the data from
the cache instead of needing to rerender
it. Now that we're actually firing these
events back within your Post Hog
dashboard, you can head over to data
management and then event definitions.
Here you'll be able to see auto capture,
exception, page leave, and so on. Now,
if you don't see the book event that we
fired from our code, this is the one I'm
talking about, the event booked to this
handle submit, we can also add e.prevent
default. So, we can check the console
just after we fire it. So, back within
our application, you can now enter your
email once again, open up the console,
and then submit it. If you do this,
you'll see that we have an error within
our code saying that only plain objects
can be passed to client components from
server components. Air objects are not
supported. So I think it's pointing to
this create booking thing. That might be
because of this booking that we're
passing here. But I just noticed that
we're not even using this created
booking. We're just using the success
and the error. So right here, there's no
need to pass over the booking once we
actually create it. The only thing we
care about is that it's in the database.
So if you remove it from there and if
you also remove this error, so we're
only passing the success of false, which
should be enough for the client side to
handle it. No longer do we have to get
the error, but rather right here under
capture exception, we can simply say
booking creation failed. Same as above.
And when we're calling this book event,
so head over to the details page where
we're passing the ID. This was supposed
to be the underscore ID. So, we get a
real ID coming from a MongoDB document.
Oh, and one thing that I remembered, if
you head over into our server action,
we're no longer even using this booking.
Rather, we're just creating it. So, we
don't have to store it into a variable
and we don't have to call the lean
method on it. There's just no need to do
it as the only thing we need is a
boolean of success. So now if you head
back to the browser, reload the page,
enter your email, but make sure to use
the email that you haven't used before,
just in case one of our previous emails
already got stored as one of the book
keys for this event. Clear the console
and submit it. Thank you for signing up.
That's great. And you can see that Post
Hog just captured a new event booked.
That's great. So if you head back to
event definitions on post hog and
reload, you should now be able to see a
new event booked custom event. So if you
click on it, you can see all the
properties
that it pulled as an example of what
this event captures. So again, in this
case, it pulled up the real example
which we sent and you can see more
information about the user who we
captured that actual event on. now to be
able to track their entire session of
coming across dev events in the first
place and then proceeding all the way to
booking. That is the perfect use case
for session replays. They should be
enabled as soon as you click on the
button within the quick start guide.
There we go. Right here for me, it
already went ahead and captured a couple
of my sessions. It actually captured an
hourong session while I was developing
the application. Oh, and alongside
viewing single user sessions, you can
also head over to heat mapaps. Heat maps
allow you to explore where your users
are clicking. To better see it, you can
open it up in toolbar, which is
basically your application, but at the
bottom, you'll also have some additional
post hog functionalities, such as the
heat map open right here, where you can
see exactly where your users are
clicking. Looks like I've been clicking
pretty randomly across the page. So, if
you head over to the page, you should be
able to see that most users clicked the
submit button, which is not really the
case here as I've been just testing the
application. There's also the web vitals
right here, which should be pretty good
because we're using the latest version
of Nex.js and most of these pages are
server side rendered and also cached.
So, all of our metrics should be pretty
fast. And back within the post hogs
dashboard, if you head over to error
tracking, remember how earlier on we
turned on error tracking so we can track
some errors. Well, during the process of
development, there were many. I can see
this reference error being repeated five
times across two sessions. So if you
click on it, you'll be able to see when
it first happened, when it was last
seen, so potentially it got fixed in the
meantime, and you can define the status
as either active or resolved. You get
the full stack trace of the error, the
properties, so you know which browser or
operating system your user was using, as
well as the session related to that
user. So if you click the recording,
you'll be able to see exactly where the
error happened throughout the user's
process of going through the app. In
this case, it might take some time to
buffer since this was an hour-ong
session, which I honestly doubt people
are going to explore dev events for an
hour. But hey, here you have it. Post
hog can handle even hour-ong sessions.
And that brings me to the end of this
build. Throughout this demo course, you
learned how to build a simple yet
effective and well-designed application.
We went through the process of creating
some API endpoints for creating
different events and we used an API
client to actually submit them. Then we
fetched those events right here,
displayed them and rendered the event
details page within it. We also created
another server action to fetch all the
similar events. So here's where we
learned a bit about server actions. Then
we learned about caching this entire
page. So it's not making a request to
the database every single time, but
rather once per hour. You learned how to
integrate Posthog for data tracking and
analytics. You also learned how
developers nowadays use AI to their
advantage through warp. And let's not
forget that throughout the process of
learning Nex.js, you also set up your
own MongoDB Atlas database that actually
keeps track of all of our events. And
the code is super clean thanks to code
rabbit as we were able to review all of
our PRs in detail which code rabbit made
significantly simpler with the summaries
walkthroughs and of course the code
reviews. The only thing that's remaining
is to deploy our application. So let's
do that next.
Let's get our app on the internet.
Before we go ahead and deploy it, head
over to your nextconfig.ts ts and type
in typescript within the object of
Typescript. You can say ignore build
errors and set it to true. The process
of building this app was long and it's
possible that we have a little
TypeScript warning here and there. We
don't want those warnings to break the
build. So this just saves us from that.
Now with that done, we have to push this
change and all of the other changes
we've made that we haven't yet pushed
over to GitHub. So, open your terminal,
type git add dot get commit- m finalize
the app and then get push.
Once you do that, back within your
browser, head over to versel.com and
create a new project. You should be able
to see that your dev events has new
changes just a couple of seconds ago.
So, go ahead and import it. You can
select your project name such as dev
events and click deploy. We'll update
our environment variables as soon as we
get the production URL. Oh, looks like
we have to enter a lowercase name. So,
that's going to be dev events. And we're
deploying. Let's give it a minute and
I'll be right back. Very quickly, you'll
see that your build will fail. And
that's because our app cannot find our
base URL.
So, it finds undefined and breaks. That
is totally expected. Head over to your
projects under domains. And here you can
see where your project will be deployed
once the build actually succeeds. So go
ahead and copy this domain name. Then
head over to settings environment
variables and you have to add a next
public
base URL and set it equal to this URL
right here. Then back within our IDE,
let's grab the rest of the environment
variables. everything besides the public
base URL. So that is next public post
hog key and host as well as the MongoDB
and cloudinary URLs. Then simply click
add another and paste them here. Once
that is done, go ahead and click save
and redeploy. I remember the times when
there wasn't this redeploy button right
here. So you'd have to go ahead and
manually remember to redeploy it. But
nowadays uh Versel team makes everything
so much easier. So now if you head over
to deployments, you'll be able to see
that your new build is well building and
hopefully this time it succeeds. Let's
give it a moment and I'll be right back.
Oh, and while the build is building, I
got to tell you a bit more about the
ultimate NexJS course. Because so far in
this crash course, you've already
learned a ton. From setting up
analytics, databases, and AI tools to
creating APIs using server actions, and
optimizing performance with caching.
These are the core fundamentals that
every great NexJS developer needs to
master. But that is just the foundation
to truly use Next.js GS for production
grade applications, you need to go a bit
deeper. I'm talking past bad performance
and actually approaching it like a
powerful framework that it is. We'll
deep dive into authentication with both
email and password as well as social
authentication. AI integrations. We'll
have custom logging functions, error
handling, database integration
pipelines transactions filters sort
queries. There's so much stuff that's
happening within this dev overflow
application which is the star of the
show. So if you're ready to take the
next step, check out the ultimate NexJS
course which more than 10,000 developers
already joined and they love it. It's
even used as professional training at
some big companies. So if you're ready
to go beyond the basics with this
superdetailed course curriculum, head
over to jsmastery.com. If you head over
there right now, you'll see that there's
a special bundle going on where you can
test your NexJS applications with this
limited time bundle deal.
With all that good talk, we are back to
deployments and I can again see that
another deployment has failed. I got to
be honest, this is Nex.js specific.
Never before have I seen these types of
errors. And I'm actually super glad that
they are happening because we get a
chance to go through these and I can say
this for a fact, never before seen
errors, right? Because NexGS 16 is super
new. It says right here that unexpected
token the deployed is not valid JSON.
This isn't really giving us anything,
right? Then it seems like it's pointing
to the homepage. Okay. uh because this
is the code from the homepage. But then
the actual error and it says it right
here occurred pre-rendering the page
events slug and then it points to the
error message right here pre-render
error and it says event slug uncashed
data was accessed outside of suspense.
This delays the entire page from
rendering resulting in a slow user
experience. And then they give us a link
right here on the blocking route
documentation page. Now if we actually
head to one of these documentation
pages, specifically the uncashed data
was accessed outside of suspense. You
can see that it is so common. It is
actually listed as one of the
documentation pages in the new version
of Nex.js. It says this error occurs
when the cache components features is
enabled. Yes, we enabled it in the next
config and Nex.js GS expects a parent
suspense boundary around any component
that awaits data that should be accessed
on every user request. The purpose of
this requirement is so that the NexJS
can provide a useful fallback while the
data is accessed and rendered.
The proper fix of course depends on what
data you're accessing. So here are a
couple of possible ways to fix it. One
talks about accessing data specifically
when you use the use cache functionality
right here similar to what we are doing
on our homepage and our event details
page. Then if you scroll down you'll see
that it's all about the suspense
wrapping the pages with suspense so that
it has something to load while the data
is loading. Alternatively you can add a
suspense boundary above the component
that is accessing the request headers.
And here we can dive a bit deeper into
the params and search param story.
Layout params, page params and search
param props are promises. We know that,
right? Because we are getting them right
here and we are already awaiting them.
But then they continue to say that if
you await them in the page component,
you might be accessing these props
higher than is actually required. So try
passing these props to deeper components
as a promise and awaiting them closer to
where the actual param or search param
is required. So you can see one of the
changes here is once again passing them
to the inner component and then wrapping
the entire component with suspense. So
before we continue reading to the docs,
let's actually do what we just read.
Back within our application, I will copy
everything starting with let event all
the way down to the bottom of the return
statement like this. It'll basically
render the event details page empty.
Like it is not returning anything, but
we can just make it return like a basic
div for now.
The code that you copied, we're going to
move over to a new component. So create
a new component in the components folder
and call it event details.tsx.
Within here you can run rafce to quickly
generate a new component. And then right
here at the top you can paste the code
you just copied. Just make sure that
this is also an asynchronous component
and that you're properly closing
everything so that it looks good. Then
you'll also have to import all of the
additional components such as the event
detail item, event agenda, and event
tags. So I'll get those from the page.
This page should not be concerned with
handling those. We'll just copy them
alongside the base URL
and use them right here within the event
details component, not the event details
page. There we go. Now everything is
here. Then this component will be
dstructuring the params similar to what
we've done before. That'll be params of
a type promise that resolves in a string
like this. Then here we'll also move the
caching mechanism use cache cache life
and const slug basically rendering the
event details page completely empty for
now. Make sure to import cache life from
next cache and then slug will be
directly coming from the params this
time. So what we've done now is we have
extracted all the logic and presentation
from the event details page into the
event details component. Now we can head
back over to the event details page and
I'll completely clear it up of all of
the imports that we no longer need. You
can see now it's a very simple page and
I guess that's the point of Nex.js.
That's what they want to achieve. They
want to make it so that page is now a
static shell that wraps around dynamic
content. Let me explain what I mean in
code because it's super confusing.
Within here, we are again destructuring
the params that are of a type promise
which result in a string. But now we'll
get to that slug a bit differently. For
this, we can refer once again to the
docs. Check this out. It looks super
weird. I'm not going to lie. Before we
were just able to await the params and
just feed them into the whatever
function we wanted. But now what you
have to do is get them like what we have
done in the previous component right
into its own component. For us that was
the event details but then within the
actual page you have to add the search
params then
and then get them like that. Super
confusing but let's follow what they say
within here. These are params. So we can
say const slug is equal to params dot
then then we get an individual I guess
we can call it param or p and then we
return the p. slug. Okay. Then within
here we'll return a main div as we're
returning a page. Within it I'll render
a suspense. This is a component coming
from React which allows us to display a
fallback while the inner contents are
loading. So I'll say fallback is equal
to and I'll set it just to be equal to a
div that says loading like this. While
things are loading, we can start to
render the event details component. This
one right here coming from components
event details. And as params, we can
pass the slug. So now that our component
which is caching the data is wrapped
within suspense. We should be good. And
now just before we retry the deployment,
I want to head over to our homepage for
a second and see here that our base URL
on localhost is pointing to localhost.
But we have already updated it to the
final deployed URL within environment
variables on Verscell. But the app never
actually deployed. So it'll not be able
to make a fetch request to API events.
For that reason, what we can do is make
another push that is just going to
result in a successful deployment by
commenting out the response and events
and then importing events coming from
lib constants. That way we're going to
render the fake events for now, but then
once the URL is deployed, we'll be able
to uncomment this and do another push.
So with all of these changes implemented
according to next.js16 docs we can now
run git add dot getit commit-m I'll say
comply with v16 gods and get push. Now
if you head back over to your
deployments you'll be able to see that a
new deployment is cued or for you maybe
it already started building. And there
we go it deployed. So now if you head
over to the overview, you'll see if you
click visit
that we're online with our six fake
events. So now that we're actually
online, we can use this URL within the
environment variables to load real
events from the database. The only thing
we have to do is uncomment these two
lines, remove the fake events, and make
another push. So run git add dot get
commit load real events and push. This
will re-trigger the deployment but this
time with real events. And would you
look at that? It looks like it succeeded
again. So if you again head over to
visit this page this time it loads real
events. And here you can see the
suspense as it's loading the event for
the first time. There we go. The event
description loaded. But you might have
seen that it took a bit of time to
actually load the page. Sure, you could
add a better suspense with some
skeletons, loaders, and so on. That's
exactly what we do in the Dev Overflow
course, but still it felt a bit too slow
regardless. So, here's one pro tip. Head
over to MongoDB Atlas, head over to your
project, and then clusters. And under
clusters, you can see the region of your
cluster. That is where your database is
hosted. you most likely chose the
version that is closest to you, whether
it's Frankfurt for Europe or something
else for the US. What matters here is
that whichever region you chose is also
the same region or very close region to
the region where your website is
uploaded. So on Versel, head over to
settings, type region right here, and
then go to function regions. You can see
here it's using North America for me
whereas for my MongoDB Atlas, I think I
chose Frankfurt. So, I'll just turn it
on. I think you can turn on up to four
different regions. And then you will
have to redeploy one more time. And then
when you switch between pages or load
some data, your server will make a
request to the database much quicker
because they're going to be much closer
together physically. So, let's wait
until it deploys and we can check it
out. And there we go. The redeployment
is out. So, if you visit it once again,
oh, that felt much faster. And if I
click right here, it loads a bit faster.
And we can also check out some other
pages. And the next time you visit them,
it should be almost instantaneous.
Keep in mind that this is now cached. So
even if you make some changes to it, you
won't be able to see new events for
about an hour, which is totally okay
because this is the type of websites
that people don't expect the real-time
updates on. Rather, we want to make it
efficient and want to make it load fast,
which is the whole point of this caching
rehole in NextG16.
But of course, that's the topic that
requires a much deeper deep dive than
this. So, as soon as I press stop from
recording this video, I'm moving over to
adding additional modules to the updated
Ultimate Nex.js GS16 course where
there's going to be a whole new module
on just caching added to our dev
overflow application. With that in mind,
thank you so much for coming to the end
of this video. If you liked it, you'll
enjoy the ultimate next.js course. It
allowed me to go into that much more
depth. So, if you decide to join,
welcome. And if not, I'll see you in the
next video. Have a great day.
In this course, youβll master Next.js 16, the latest version of the official React framework, and learn how to build powerful, full-stack applications using its new caching and performance features. Youβll put your skills into practice by creating and deploying a Dev Event Platform, a modern app where users can browse, create, and manage events. PostHog: https://jsm.dev/devevent-posthog WebStorm: https://jsm.dev/devevent-webstorm Junie AI: https://jsm.dev/devevent-junie Warp: https://jsm.dev/devevent-warp CodeRabbit: https://jsm.dev/devevent-coderabbit CodeRabbit CLI: https://jsm.dev/devevent-coderabbitcli Cloudinary: https://jsm.dev/devevent-cloudinary The Complete Testing Course is LIVE. π₯ Bundle up now before the deal expires and get 33% OffβΌ π https://jsm.dev/devevent-testing π GSAP Pro Course (includes GTAVI Website): https://jsm.dev/devevent-gsap π Three.js 3D Pro Course: https://jsm.dev/devevent-threejs π JavaScript Pro Course: https://jsm.dev/devevent-cpjsm π Launch Your SaaS Pro Course: https://jsm.dev/devevent-saas βΌΒ If the links arenβt working for you, please try using a VPN (e.g., in Nigeria) π FREE Video Kit (Code, Figma, Assets, Etc.): https://jsm.dev/devevent-kit π Backend Pro Course Waitlist: https://jsm.dev/devevent-backpro π AI Development Pro Course Waitlist: https://jsm.dev/devevent-aipro π Tailwind Pro Course Waitlist: https://jsm.dev/devevent-twpro π React.js Pro Course Waitlist: https://jsm.dev/devevent-reactpro π React Native Pro Course Waitlist: https://jsm.dev/devevent-nativepro Rate us on TrustPilot: https://jsm.dev/trustpilot https://discord.com/invite/n6EdbFJ https://twitter.com/jsmasterypro https://instagram.com/javascriptmastery https://linkedin.com/company/javascriptmastery Business Inquiries: contact@jsmastery.pro Time Stamps: 00:00:00 β Introduction 00:04:09 β Crash Course Intro 00:08:30 β Installation 00:11:45 β Project Structure 00:14:19 β App Router 00:17:16 β React Client & Server Components 00:23:40 β Routing 00:42:42 β Data Fetching 00:48:53 β API Routes 00:52:45 β Caching 00:56:05 β Build Adapters API 00:57:24 β Metadata 01:01:23 β Theory to Practice β β β 01:02:48 β Project Setup 01:18:01 β Homepage 01:41:35 β PostHog Setup 01:50:21 β MongoDB Setup 01:52:57 β Warp Setup 01:56:46 β Database Models & Connection 02:09:36 β CodeRabbit 02:16:11 β API Routes 02:27:03 β Image Upload 02:37:51 β Fetch API Routes 02:54:47 β Event Details Page 03:14:34 β Server Actions 03:28:05 β Caching 03:39:46 β PostHog: Booking Action & Tracking 03:54:05 β Deployment 03:58:15 β Outro