We learned that Next.js pre-renders
our components on the server.
We can see that with a simple log message:
when we request the HomePage in the browser,
the "rendering" message is printed
only in the server logs,
and not in the browser console.
This is also different from how Next.js
works with the older Pages Router.
The new App Router by default
uses React Server Components.
This is a feature recently added to React
that allows us to create components that
are rendered only on the server,
without sending any JavaScript
code to the browser.
That's what "Zero-Bundle-Size" means:
they're components that add zero
code to the JavaScript bundle.
Let me show you how this is different
from the previous Next.js approach.
I have prepared another project here,
that is still based on Next.js,
but using the Pages Router.
Note how, rather than having an "app" folder,
in this project there is a "pages" folder.
But the dependencies are exactly the
same as our first Next.js project.
Using the Pages Router there
is a file called "_app.jsx",
that plays a similar role to the
RootLayout for the App Router.
And the HomePage is in "index.jsx".
It's very similar to our other HomePage.
Now, we can start the server
with "npm run dev" as usual,
and it's running on port 3001.
You can see that the page is almost the same.
Now, let's see what happens if we clear the logs
and reload the page in the browser.
You can see that the HomePage component
is rendered on the server,
just like with the App Router,
but then it's also rendered in the browser.
This is in fact the main difference
between the old Pages Router
and the new App Router.
The Pages Router always renders all components
both on the server and in the browser.
While the App Router by default only
renders them on the server.
But why does the Pages Router render
them in the browser as well?
The reason is that in a React component we
may use some client-side functionality.
Just as a quick example,
suppose we want to display a pop up window
as soon as the page is loaded.
We could do that by calling the "useEffect" hook,
that lets us execute a function after
the component is rendered.
If we pass an empty array as the dependency list,
that's the second argument,
our effect function will only run once,
after the component is rendered
for the first time.
Now, to keep this simple I'll
call "window.alert" here,
and show a message saying "Welcome to my site!!!".
If we save this change,
you can see that we get this alert straight away.
And this will show up any time we load the page.
Obviously, this is a rather silly example.
Displaying a popup as soon as the page
loads is a horrible user experience.
But sometimes you do need to display
a cookie consent banner or similar.
Anyway, the point is that the code that
shows the alert must run in the client,
it cannot run on the server, because
there's no browser window there.
That's why Next.js re-renders the HomePage
component in the browser.
Let me show you in more detail how this works.
When we load the page, the browser requests
the HTML document as usual.
And, even when using the Pages Router,
you can see that Next.js pre-renders the HTML.
That's why it renders the component on the server.
But then it will also send some JavaScript
files to the browser,
including our code that triggers
the "window.alert".
Which is part of our HomePage component function.
So, Next.js first renders our
component on the server
to generate the pre-rendered HTML
returned in the response.
But then it also sends the same component
code in a JavaScript bundle
so that we can run client-side functionality,
like displaying an annoying popup to the user.
The limitation of the old Pages Router
is that it always sends to the browser
all the code for all our components.
If you remember, it was re-rendering
the HomePage in the browser
even before we added the popup window,
when the HomePage was entirely static.
That was unnecessary.
As we've seen, the new App Router uses
React Server Components instead
that are only rendered on the server,
without sending any JavaScript
code to the browser.
And that's a lot more efficient.
But, it also begs the question:
what happens if we need some
client-side functionality
in a project that uses the App Router?
Let's try, and we'll find out.
I just need to import "useEffect" here.
And if we save,
it doesn't work!
We get a "ReactServerComponentError".
There are some more details in the console.
It says "You're importing a component
that needs useEffect."
"It only works in a Client Component"
"but none of its parents are
marked with use client"
"so they're Server Components by default".
Let's unpack this message.
It contains three useful pieces of information.
First: our HomePage is currently
a Server Component,
because that's the default.
Second: to use client-side functionality
(like useEffect)
it needs to be a Client Component instead.
And third: we can make it a Client Component
by marking it with "use client",
that is a special directive we
can add at the top of a file.
With this simple change, our
page is working again,
and it's displaying the popup window,
so the client-side functionality also works.
So, simply by adding "use client" at the top,
this is now a Client Component rather
than a Server Component.
But, what does this mean exactly?
Let's clear the logs, and see how
a Client Component is rendered.
You can see that it's rendered both
on the server and in the browser.
So it works pretty like with the old Pages Router.
The difference is that with the new App Router
we need to explicitly declare if we
need client-side functionality
by adding the "use client" directive at the top.
Without it, it will be treated
as a Server Component,
meaning that it will only be
rendered on the server,
without sending unnecessary JavaScript
code to the browser.
To recap, in Next.js with the App Router
there are two different kinds of
components: Server and Client.
Server Components are the default,
which means that all our components
will be Server Components,
unless we specify otherwise.
If we want a Client Component instead,
we need to put the "use client" directive
at the top of the file.
Server Components are only rendered on the server,
so their code is not sent to the browser,
making them more efficient, which
is why they're the default.
Client Components on the other hand are
still pre-rendered on the server
but then they are also rendered in the browser.
This allows them to include
client-side functionality,
something that's not possible
with Server Components.
So in a Server Component we cannot use React hooks,
like useEffect,
nor event handlers, like the
"onClick" on a button,
nor any browser APIs, like accessing
the "window" object.
On the other hand, as we'll
see later in the course,
Server Components can potentially
use server-side functionality,
like Node.js built-in modules for example.
Something that's obviously not
possible in the browser.