25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/app/movies/[id]/page.js:
--------------------------------------------------------------------------------
1 | import { experimental_use as use } from "react";
2 |
3 | async function getMovie(id) {
4 | let res = await fetch(`http://localhost:3001/movies/${id}`);
5 |
6 | return res.json();
7 | }
8 |
9 | export default function Page({ params }) {
10 | let movie = use(getMovie(params.id));
11 |
12 | return (
13 |
14 |
{movie.title}
15 |
Year: {movie.year}
16 |
{movie.description}
17 |
18 | );
19 | }
20 |
21 | export async function generateStaticParams() {
22 | let res = await fetch("http://localhost:3001/movies");
23 | let movies = await res.json();
24 |
25 | return movies.map((movie) => ({ id: movie.id }));
26 | }
27 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "movies": [
3 | {
4 | "id": "1",
5 | "title": "Lord of the Rings",
6 | "year": 2001,
7 | "description": "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron."
8 | },
9 | {
10 | "id": "2",
11 | "title": "Star Wars",
12 | "year": 1977,
13 | "description": "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee and two droids to save the galaxy from the Empire's world-destroying battle station, while also attempting to rescue Princess Leia from the mysterious Darth Vader."
14 | }
15 | ],
16 | "cast": [
17 | { "id": "1", "name": "Elijah Wood", "movieId": "1" },
18 | { "id": "2", "name": "Ian McKellen", "movieId": "1" },
19 | { "id": "3", "name": "Orlando Bloom", "movieId": "1" },
20 | { "id": "4", "name": "Mark Hamill", "movieId": "2" },
21 | { "id": "5", "name": "Harrison Ford", "movieId": "2" },
22 | { "id": "6", "name": "Carrie Fisher", "movieId": "2" }
23 | ],
24 | "reviews": [
25 | { "id": "1", "text": "It was awesome.", "author": "Sam", "movieId": "1" },
26 | { "id": "2", "text": "I liked it.", "author": "Ryan", "movieId": "1" },
27 | {
28 | "id": "3",
29 | "text": "The greatest kids' picture for adults since \"The Wizard of Oz.\"",
30 | "author": "Tom Shales",
31 | "movieId": "2"
32 | },
33 | {
34 | "id": "4",
35 | "text": "On the basic level of simple entertainment it succeeds as well as any film ever has.",
36 | "author": "Steve Biodrowski",
37 | "movieId": "2"
38 | }
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/SCRIPT.md:
--------------------------------------------------------------------------------
1 | # Talk
2 |
3 | Hello world of Next 13!
4 |
5 | # Step
6 |
7 | Let's add tailwind:
8 |
9 | ```js
10 | // layout.js
11 | import "tailwindcss/tailwind.css";
12 | ```
13 |
14 | No more \_app or \_document! Add classes right to html.
15 |
16 | ```
17 |
18 | ```
19 |
20 | # Step
21 |
22 | Right now we've got one page. Let's go ahead and add a page for the `/movies` URL.
23 |
24 | ```jsx
25 | // movies/page.js
26 | export default function Page() {
27 | return
Movies page
;
28 | }
29 | ```
30 |
31 | # Step
32 |
33 | Now let's add a header with some links. And the root layout is the perfect place for persisent UI like our header.
34 |
35 | ```jsx
36 |
37 | Home
38 | Movies
39 |
40 |
41 | {children}
42 | ```
43 |
44 | Links work and the header is persistent!
45 |
46 | Let's turn these into next/links. Now we have clientside transitions.
47 |
48 | # Step
49 |
50 | Ok, it'd be nice to see which link is active. Let's come to our root layout and drop a console.log here whenever this component renders.
51 |
52 | ```jsx
53 | console.log("rendering");
54 | ```
55 |
56 | We don't see this running whenever we navigate the page. But we actually also don't see it when we refresh the page. But if we pop over to our terminal, we actually see it running here!
57 |
58 | This is because components in Next 13 are Server Components by default! This is different from Next 12, which pre-rendered React components at build time but then shipped them to the browser to be executed on the client. Server Components actually execute at the time of the request on the server, and ship zero javascript to the client, which is great for making our apps smaller and faster, in addition to some other benefits we'll cover soon.
59 |
60 | But for these links, since we want them to be interactive and change their style as we navigate, we want some good ol' React components right here in the browser.
61 |
62 | So let's create a new component called NavLink. And we can colocate it right here next to our root layout, which is another great feature of Next 13.
63 |
64 | ```jsx
65 | export default function NavLink() {
66 | //
67 | }
68 | ```
69 |
70 | We'll paste in our link, and lets go ahead and take in our href and children as props.
71 |
72 | ```jsx
73 | import Link from "next/link";
74 |
75 | export default function NavLink({ href, children }) {
76 | return (
77 |
78 | {children}
79 |
80 | );
81 | }
82 | ```
83 |
84 | And replace them in the layout. So now if we save this, we'll see everything works.
85 |
86 | Now let's come to our NavLink. Right now this is still a server component: if we add a log we don't see it in the browser:
87 |
88 | ```jsx
89 | console.log(href);
90 | ```
91 |
92 | But we can turn it into a client comopnent with `use client`. Check that out. We see our log in the browser!
93 |
94 | Now that this is a client component, we have some useful new hooks we can use here. The one we want is useSelectedLayoutSegment.
95 |
96 | ```jsx
97 | let selectedSegment = useSelectedLayoutSegment();
98 | console.log({ href, selectedSegment });
99 | ```
100 |
101 | We see as we navigate this shows us which segment the current layout is rendering – basically, what's being rendered into `children` right here in our layout. And we can see when we're on `/movies` the segment is `movies` and when we're on `/` the segment is the empty string. So we should be able to see if a link is active if its href is equal to slash the segment:
102 |
103 | ```jsx
104 | let active = href === `/${selectedSegment}`;
105 | ```
106 |
107 | And now we can use this in our Link:
108 |
109 | ```jsx
110 |
111 | ```
112 |
113 | Boom! Active class right here.
114 |
115 | I really love how easy this is, to interleave client components and server components with each other. It's a big part of how Next 13 lets us use the full power of React whenever we need it, while keeping the static parts of our app slim and fast by rendering them on the server.
116 |
117 | ## Step
118 |
119 | Ok, it's time to fetch some data! I have an API running - if we pull it up we can see a list of movies at `http://localhost:3001/movies`.
120 |
121 | So if we come to our /movies page, how might we fetch this data? getServerSideProps? useEffect?
122 |
123 | How about an async function called getMovies?
124 |
125 | ```js
126 | async function getMovies() {
127 | let res = await fetch("http://localhost:3001/movies");
128 |
129 | return res.json();
130 | }
131 | ```
132 |
133 | ```jsx
134 | export default function Page() {
135 | let movies = use(getMovies());
136 |
137 | return (
138 |
139 |
140 | {movies.map((movie) => (
141 |
{movie.title}
142 | ))}
143 |
144 |
145 | );
146 | }
147 | ```
148 |
149 | This is still running on the server! Benefits: fewer client states to deal with - take advantage of request/reseponse cycle. Direct access to server resources.
150 |
151 | > Maybe? Enable slow 3g. Click movies. No response - pretty bad. If we don't want the load-then-render of a traditional server rendered site we can add a loading page. Cool! Instant navigation.
152 |
153 | # Step
154 |
155 | Ok - it's time to make that list-detail view we saw from the beginning. We want this list of movies to be in a sidebar, and the current movie to show up here, something like this:
156 |
157 | ```
158 |
159 |
160 | {movies.map((movie) => (
161 |
{movie.title}
162 | ))}
163 |
164 |
165 |
166 |
Lord of the Rings
167 |
168 |
169 | ```
170 |
171 | Looks like another layout! Movies in layout, and selected movie renders right here as `children`.
172 |
173 | So let's do that. Let's make this whole thing a layout. And layouts get children. And if we save and reload we see a 404 since we don't have a page here. So let's make one
174 |
175 | ```jsx
176 | export default function Page() {
177 | return
Hi
;
178 | }
179 | ```
180 |
181 | and now `/movies` is visible again.
182 |
183 | So this page is whats rendered when we're just at /movies - its sort of the index for /movies. And you might use this to have a message, something like "Select a movie!"
184 |
185 | ## Step
186 |
187 | Ok so now we want to make these links go to specific movies using their id – something like `/movies/1` and `/movies/2`.
188 |
189 | So let's make these links go to `/movies/id`.
190 |
191 | ```jsx
192 |
193 | {movie.title}
194 |
195 | ```
196 |
197 | and now when we click lotr, we go to /movies/1 and we get a 404.
198 |
199 | So let's make this page by using a dynamic segment. The way we do that is by making the folder name with brackets (just like in Next 12, but its called page.js)
200 |
201 | ```jsx
202 | // movies/[id]/page.js
203 |
204 | export default function Page() {
205 | return
This is a movie
;
206 | }
207 | ```
208 |
209 | And now we're rendering this both at /movies/1 and /movies/2.
210 |
211 | Ok so we want this page to be different for each movie – how do we get which id we're at?
212 |
213 | Dynamic pages get a `params` object as an arg so if we log that out
214 |
215 | ```jsx
216 | export default function Page({ params }) {
217 | console.log(params);
218 |
219 | return
Movie {params.id}
;
220 | }
221 | ```
222 |
223 | now we can see which id we're on.
224 |
225 | - Nested layouts recap! Good way to build ui.
226 |
227 | ## Step
228 |
229 | To show the movie details we need to actually fetch this movie from the server. We know how to fetch data - copy from layout.
230 |
231 | ```jsx
232 | import { experimental_use as use } from "react";
233 |
234 | async function getMovie(id) {
235 | let res = await fetch(`http://localhost:3001/movies/${id}`);
236 |
237 | return res.json();
238 | }
239 |
240 | export default function Page({ params }) {
241 | let movie = use(getMovie(params.id));
242 |
243 | return
{movie.title}
;
244 | }
245 | ```
246 |
247 | and all this is still running on the server!
248 |
249 | ## Step
250 |
251 | Let's update our links in our template here to show which movie is selected. We'll create a `movie-link` right here and copy over our nav link. Change to MovieLink, and let's take a look at `selectedSegment` on this one.
252 |
253 | ```jsx
254 | console.log(selectedSegment);
255 | let active = href === `/movies/${selectedSegment}`;
256 | ```
257 |
258 | And now can customize the active state:
259 |
260 | ```
261 | className={active ? "text-gray-100" : "hover:text-gray-300"}
262 | ```
263 |
264 | And it works!
265 |
266 | ## Step
267 |
268 | Ok – let's add some more details from our movie!
269 |
270 | ```
271 | return (
272 |
273 |
{movie.title}
274 |
Year: {movie.year}
275 |
{movie.description}
276 |
277 | );
278 | ```
279 |
280 | And lets see how deep the nesting rabbit hole goes by adding another chunk of UI here to show the cast or the reviews for the movie.
281 |
282 | ```jsx
283 |
287 | ```
288 |
289 | You know the drill – let's turn this page into a layout, grab the `children` and render them right here. And let's go ahead and make a page for the cast members. I can fetch the cast members for a movie via `http://localhost:3001/movies/1/cast`, so let's fetch some data
290 |
291 | ```js
292 | import { experimental_use as use } from "react";
293 |
294 | async function getCast(movieId) {
295 | let res = await fetch(`http://localhost:3001/movies/${movieId}/cast`);
296 |
297 | return res.json();
298 | }
299 |
300 | export default function Page({ params }) {
301 | let members = use(getCast(params.id));
302 |
303 | return (
304 |
305 |
306 | {members.map((member) => (
307 |
{member.name}
308 | ))}
309 |
310 |
311 | );
312 | }
313 | ```
314 |
315 | Now, let's turn these into links:
316 |
317 | ```jsx
318 |
319 | Cast
320 |
321 |
322 | Reviews
323 |
324 | ```
325 |
326 | and build the reviews page. And finally the reviews page. Copy from Cast.
327 |
328 | And look at that. We've got the root layout rendering the movies layout rendering the movies/[id] layout, which renders either the cast or reviews page. And each segment – each layout or page – is loading the data that it needs, right here alongside of it. So this makes for a super easy way to break up our UI, we don't need to go up to the top of the route and keep tweaking a single loading hook like getServerSideProps in Next 12, to make sure it gets data for these nested segments, and then flow that data down. We can co-locate all the data fetching with each segment that needs it.
329 |
330 | Pretty cool!
331 |
332 | ## Step
333 |
334 | Now this whole time we've been building you might be wondering about features from the current version of Next like getStaticProps and ISR – some of next's killer features that make the static parts of our sites super fast.
335 |
336 | Well let's come down to our terminal here, start api, run a build. Instant, movies loaded, API server is silent. Pretty amazing because we wrote this layout code right here with fetch! Right in our component. But because of RSC and suspense, Next can actually render these components at build time and wait for them to unsuspend, before completing the build. So we're getting the equivalent behavior of getStaticProps here, but we're able to just write React components using RSC and the `use` hook.
337 |
338 | Now if we click on a movie, we're going to see we're still fetching this data here. And this is becuase of the dynamic segment. Now in Next 12 we had the `getStaticPaths` API that we could use if we wanted to pregenerate these pages at build time.
339 |
340 | In Next 13 we have a similar api, called `generateStaticParams`. So if we come to the layout for this dynamic segment
341 |
342 | ```js
343 | export async function generateStaticParams() {
344 | let res = await fetch("http://localhost:3001/movies");
345 | let movies = await res.json();
346 |
347 | return movies.map((movie) => ({
348 | id: movie.id,
349 | }));
350 | }
351 | ```
352 |
353 | Similar to paths but only concerned with the current dynamic segment. So we can fetch our movies and return an array of movie ids that we want to pregenerate. Rebuild, boom.
354 |
355 | Beyond scope of talk but there's way more options for configuring these fetch calls, ways to signal to Next how each one should be cached, whether it should be run at build time or revalidated every 10 seconds or via a webhook using ISR, but regardless very exciting how we basically have just one way to do fetch data, using fetch in a RSC, and then we can mix and match these options to get a fast site or real-time data.
356 |
357 | - Been making nested layouts for years, great way to build apps
358 |
--------------------------------------------------------------------------------