Skip to content

Commit

Permalink
feat(C8): finalize C8 (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
uhasker authored Dec 24, 2024
1 parent ef7b6c3 commit 67b63e8
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 53 deletions.
34 changes: 27 additions & 7 deletions src/chapter8/01-setup.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
## Setup

<div style="text-align: right"> <i> What's a Next.js developer's favorite part of setting up a new project? <br> Installing dependencies. It's like Christmas, but every package is a surprise! <br> - From "1000 programming dad-jokes" </i> </div>
<div style="text-align: right"> <i> What's a Next.js developer's favorite part of setting up a new project? <br> Installing dependencies. It's like Christmas, every package is a surprise! <br> From "1000 programming dad-jokes" </i> </div>

### Creating a Next.js Project

Run the following command to create a new Next.js project:
In this section, we will setup a clean Next.js project for the following sections.

Let's run the following command to create a new Next.js project:

```sh
pnpm create next-app
Expand All @@ -17,9 +19,7 @@ Give your project a name and select the following options:
- we want to use Tailwind CSS
- we want to use the `src/` directory
- we want to use the App Router
- we don't want to customize the defalt import alias

Next, navigate to the newly created directory and run:
- we don't want to customize the default import alias

```sh
pnpm dev
Expand All @@ -28,11 +28,31 @@ pnpm dev
> Note that `pnpm create next-app` automatically runs `pnpm install`, so you don't need to worry about that.
If you go to `http://localhost:3000`, you will see the default home page.
Let's simplify it a bit for the following sections.
But where is this home page coming from?

Looking at our new Next.js project, we will see the following layout:

```
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── public
├── README.md
├── src
│   └── app
│   ├── ...
│   ├── layout.tsx
│   └── page.tsx
├── tailwind.config.ts
└── tsconfig.json
```

Crucially, our code is contained in `src/app` and our home page is located at `src/app/page.tsx`.
Let's clean it up a bit.

### Simplifying the Default Project

Enter the `src` directory.
Enter the `src/app` directory.

First, remove the `globals.css` file (styles will be the topic of the next chapter).

Expand Down
35 changes: 22 additions & 13 deletions src/chapter8/02-pages-and-layouts.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
## Pages and Layouts

<div style="text-align: right"> <i> Why do Next.js pages make lousy customs officers? <br> Because they keep exporting everything. <br> - From "1000 programming dad-jokes" </i> </div>
<div style="text-align: right"> <i> Why do Next.js pages make lousy customs officers? <br> Because they keep exporting everything. <br> From "1000 programming dad-jokes" </i> </div>

### Pages

Next.js uses **file-based routing**.
Folders are used to define routes and each folder represents a route segment (which maps to a URL segment).
This means that folders are used to define routes and each folder represents a route segment (which maps to a URL segment).

You can define nested routes by nesting folders inside each other.

For example the folder `about` would represent the route `/about` (which would be mapped to the URL `/about`).
If you would have a folder `about` inside a folder `company` this would represent the route `/company/about` (which would be mapped to the URL `/company/about`).
For example, the folder `about` would represent the route `/about` (which would be mapped to the URL `/about`).
On the other hand, a folder `about` inside a folder `company` would represent the route `/company/about` (which would be mapped to the URL `/company/about`).

Note that all of this is relative to our root directory (which in our application is `src/app`).
This means that the route `/about` would actually be located at `src/app/about` and the route `/company/about` would actually be located at `src/app/company/about`.

Routes can have special files, which are used to actually define the route.
For example, the special `page` file (e.g. `page.js`, `page.jsx` or `page.tsx`) is used to display UI for the given route.
For example, the special `page` file (which can be e.g. `page.js`, `page.jsx` or `page.tsx`) is used to display the UI for the given route.
For this to work, the `page` file needs to `default` export a React component.

You already have one of these files - namely `src/app/page.tsx` which defines the UI to be displayed for the route `/`.
We already have a route with a special `page` file—namely `src/app/page.tsx` which defines the UI to be displayed for the (root) route `/`:

```jsx
export default function Home() {
return <h1>Welcome</h1>;
}
```

If you go to `http://localhost:3000/`, you will see the text `Welcome`, because that's the UI that is defined in the `default` exported React component.

Let's create another page.
Add a new directory `about` in `src/app` and create the following file `src/app/about/page.tsx`:
Expand All @@ -29,16 +38,16 @@ export default function About() {
}
```

Go to `http://localhost:3000/about` and you will see the new page.
Go to `http://localhost:3000/about` and you will see the text `About`.

> From now on we will stop prefixing everything with `src/app` and simply assume that you are always in `src/app`.
> For example, if we tell you to create a file `task/route.ts` you should actually create the file at `src/app/task/route.ts`.
### Layouts

A page is a UI that is unique to a route.
You can also define a UI that is shared between multiple pages.
This is called a **layout**.
A page defines the UI that is unique to a route.
You can also define UI that is shared between multiple pages.
This is called a **layout** in Next.js.

You define a layout by `default` exporting a React component from a `layout` file (e.g. `layout.tsx`).
The component should accept a `children` prop that will be populated with a child layout or a child page.
Expand Down Expand Up @@ -72,13 +81,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })

Now the header `"My header"` will appear on every page (i.e. both on `/` and `/about`).

> After you verified this, you can remove the header again.
> After you've verified this, you can remove the header again.
Note that the top-most layout (which must always be present) is called the **root layout** and will be shared across all pages.
The root layout must contain `html` and `body` tags.

The root layout is also where you define metadata.
This can be done by exporting a `metadata` object:
This can be done by exporting a `metadata` object in the root layout file:

```ts
export const metadata: Metadata = {
Expand All @@ -95,7 +104,7 @@ This is a component built on top of the HTML `<a>` tag that you already know.
However, this component does some additional things, like prefetching (i.e. preloading routes in the background).
It also changes the way navigation works (we will talk more about this in the next sections).

Here is how you could link the About page from the home page:
For example, you could add a link to the `/about` page from the `/` page by changing the content in `page.tsx` to this:

```jsx
import Link from 'next/link';
Expand Down
89 changes: 70 additions & 19 deletions src/chapter8/03-more-on-routes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## More on Routes

<div style="text-align: right"> <i> Why do Next.js routes make terrible secret agents? <br> Because they keep exposing endpoints. <br> - From "1000 programming dad-jokes" </i> </div>
<div style="text-align: right"> <i> Why do Next.js routes make terrible secret agents? <br> Because they keep exposing endpoints. <br> From "1000 programming dad-jokes" </i> </div>

### Dynamic Routes

Expand All @@ -9,17 +9,20 @@ Let's say you want to add a route that displays a task with a certain ID at `/ta
For example, you might wish to display a task with the ID `1` at `/task/1` and a task with the ID `2` at `/task/2`.

Of course, since tasks are added and deleted by the user, you can't know all of the IDs beforehand.
This where dynamic routes come in.
This is where dynamic routes come in.

Create a new file `task/[id]/page.tsx`:
Create a new file `task/[id]/page.tsx` with the following content:

```jsx
export default function Task({ params }: { params: { id: string } }) {
return <p>This is a task with ID {params.id}</p>;
}
```

If you go to `http://localhost:3000/task/1`, you should see the following text:
Here, you pass a `params` object that contains the `id` property.
This `id` property will contain the ID passed in the URL.

For example, if you go to `http://localhost:3000/task/1`, you should see the following text:

```
This is a task with ID 1
Expand All @@ -32,13 +35,13 @@ This is a task with ID 56789
```

Note that the `[id]` notation will only match a single segment.
This means for example `http://localhost:3000/task/1/status` will not be matched by `task/[id]` and you will see a `404`.
This means that e.g. `http://localhost:3000/task/1/status` will not be matched by `task/[id]` and you will see a `404`.

If you want to change this, you can use catch-all segments with `[...id]` and optional catch-all segments with `[[...id]]`.

### Route Handlers

Route handlers basically allow you to create API routes.
Route handlers basically allow you to create API routes (similar to what we did in the networking chapter).
Route handlers are defined by `route` files.

Let's create a new file `api/task/route.ts` and add a simple example route handler:
Expand All @@ -53,14 +56,19 @@ export async function GET() {
}
```

Try accessing the route:
You can try accessing the route by using `curl`:

```sh
curl localhost:3000/api/task
```
$ curl localhost:3000/api/task
{"taskId":1}

This will output the following JSON:

```json
{ "taskId": 1 }
```

You can access the request by passing a `request` argument to the function:
You can access the request by passing a `request` argument to the function (similar to `express`):

```ts
import { NextRequest, NextResponse } from 'next/server';
Expand All @@ -77,6 +85,7 @@ export async function GET(request: NextRequest) {
If you look at your console, you will see that the request object is logged.

You can use the `request` object to access the various request properties.

For example, you could retrieve the cookies of the current request using `request.cookies`.
This is a special `RequestCookies` object that exposes methods that you can use to retrieve cookies:

Expand All @@ -96,11 +105,19 @@ export async function GET(request: NextRequest) {
}
```

Try accessing the route now:
Try accessing the route while passing it a cookie:

```sh
curl --cookie "language=de" localhost:3000/api/task
```
$ curl --cookie "language=de" localhost:3000/api/task
{"allCookies":[{"name":"language","value":"de"}],"languageCookie":{"name":"language","value":"de"}}

The result will be:

```json
{
"allCookies": [{ "name": "language", "value": "de" }],
"languageCookie": { "name": "language", "value": "de" }
}
```

Similarly you can access the headers of a request:
Expand All @@ -121,12 +138,29 @@ export async function GET(request: NextRequest) {

Try accessing the route again:

```sh
curl localhost:3000/api/task
```
$ curl localhost:3000/api/task
{"headers":[["accept","*/*"],["host","localhost:3000"],["user-agent","curl/7.81.0"],["x-forwarded-for","::ffff:127.0.0.1"],["x-forwarded-host","localhost:3000"],["x-forwarded-port","3000"],["x-forwarded-proto","http"]],"userAgent":"curl/7.81.0"}

This will print the headers that are automatically set by `curl`:

```json
{
"headers": [
["accept", "*/*"],
["host", "localhost:3000"],
["user-agent", "curl/7.81.0"],
["x-forwarded-for", "::ffff:127.0.0.1"],
["x-forwarded-host", "localhost:3000"],
["x-forwarded-port", "3000"],
["x-forwarded-proto", "http"]
],
"userAgent": "curl/7.81.0"
}
```

You can have dynamic segments in your route handlers:
You can have dynamic segments in your route handlers.
Let's create a new file `api/task/[id]/route.ts`:

```ts
import { NextResponse } from 'next/server';
Expand All @@ -140,6 +174,18 @@ export async function GET(request: Request, { params }: { params: { id: string }
}
```

Try accessing the dynamic route:

```sh
curl localhost:3000/api/task/42
```

This will return:

```json
{ "taskId": "42" }
```

You can read query params from the `nextUrl.searchParams` object:

```ts
Expand All @@ -154,9 +200,14 @@ export function GET(request: NextRequest) {
}
```

For example:
Try accessing the route:

```sh
curl "localhost:3000/api/task?title=Title&description=Description"
```
$ curl "localhost:3000/api/task?title=Title&description=Description"
{"title":"Title"}

This should return:

```json
{ "title": "Title" }
```
31 changes: 18 additions & 13 deletions src/chapter8/04-server-and-client-components.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
## Server and Client Components

<div style="text-align: right"> <i> Why did the Next.js component get anxiety during drinking? <br> Because it was afraid of hydration errors. <br> - From "1000 programming dad-jokes" </i> </div>
<div style="text-align: right"> <i> Why did the Next.js component get anxiety during drinking? <br> Because it was afraid of hydration errors. <br> From "1000 programming dad-jokes" </i> </div>

### Rendering

Next.js renders as much as it can on the server.
By default the React components you write are rendered on the server side and the resulting HTML is sent to the browser.
However, not all components can be fully rendered on the server.
By default, the React components you write are rendered on the server side and the resulting HTML is sent to the browser.

Unfortunately, not all components can be fully rendered on the server.
For example, components that require interactivity must be partially rendered on the client.

Therefore the rendering is split into multiple stages:
Therefore, the rendering is split into multiple stages:

First, Next.js renders as much as it can on the server.

Expand All @@ -31,13 +32,13 @@ Data fetching can be moved from the client to the server which can simplify the
This is especially beneficial for clients with slow network speeds.

Additionally, dependencies are now on the server and don't need to be served to the client.
This helps greatly with reducing bundle sizes.
This helps greatly with reducing how much the server needs to send to the client.

### Server Components

**Server components** are the components that are completely rendered on the server.

For example this component is a server component:
For example, this component is a server component:

```jsx
import * as React from 'react';
Expand All @@ -47,7 +48,7 @@ export default function Task() {
}
```

There is no interactivity here, so this component can be rendered on the server completely.
There is no interactivity here, so this component can and will be rendered completely on the server.

### Client Components

Expand Down Expand Up @@ -121,9 +122,13 @@ export default function Counter() {
}
```

If you go to `http://localhost:3000/counter`, you will see that the message is output both on the server as well as the client.
If you click the button, you will see that the message is output only on the client (since the component only rerenders there).
However, if you refresh the page, you will observe that the message is again logged both to the server as well as the client.
If you go to `http://localhost:3000/counter`, you will see that the `Rendered counter` message is output both on the server console as well as on the client console.
That's because the component renders both on the server and on the client.

However, all further interactivity happens only on the client.
For example, if you click the button, you will see that the `Rendered counter` message is output only on the client console.

However, if you refresh the page, you will observe that the `Rendered counter` message is again logged both to the server as well as the client.

### When to use what?

Expand All @@ -136,6 +141,6 @@ Use server components if:

Use client components only if:

- you need event listeners (like `onClick`)
- you need to use `useState`, `useEffect` or `useReducer`
- you need to use certain browser APIs
- you need to use event listeners (like `onClick`)
- you need to use certain React hooks like `useState` or `useEffect`
- you need to use certain browser functionality (like resizing the browser window)
2 changes: 1 addition & 1 deletion src/chapter8/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
Now that you have a rough overview of React, we want to move to the server.
Next.js is a React-based framework for building full-stack applications.

You will need to use at least version 13 of Next.js, because that introduced the App Router which is built on top of React Server Components.
We will use Next.js 15, which includes the App Router and React Server Components.

0 comments on commit 67b63e8

Please sign in to comment.