Introduction to the Next.js 13 Paradigm
The most popular full stack JavaScript framework in the world is without a doubt Next.js, and just recently they announced version 13 which unleashes a brand new paradigm for writing Next apps with React Server components. Today's video is a full tutorial where we build a basic CRUD app for taking notes that takes advantage of these brand new features. It's beginner level so you'll learn a lot if you're new to writing code or if you're an experienced Next developer, you'll gain a better understanding of these new changes.
Whenever a popular framework has a major change it's very difficult for those learning to code. I saw it happen when Angular 1 went to Angular 2, when Vue 2 went to Vue 3, and now with Next 12 to Next 13. There are millions of Next tutorials and courses on the internet but as of this week they're all outdated. Well, kind of. These new features can be incrementally adopted, which means you can still write Next code the old way. The bad news is that learning Next.js in 2023 will be a little more confusing because there are now two different ways to do essentially the same thing. Also keep in mind that these new features are currently in beta so if anything I show you today doesn't work, it's not my fault. Go and read the docs.
Project Setup with Next.js and PocketBase
To get started we'll first want to generate a new Next 13 app with a CLI and I'll be using TypeScript with the TS flag. Now I want to build a real full stack app here and that means we need some kind of backend database. For that we'll be using PocketBase, which in my opinion is the ultimate backend for side projects. It's incredibly easy to use and can be a great option for production as well. To use it simply go to the website and download its executable into the root of your project. Now open the terminal and run it with its serve command. It'll give you a link to the admin UI where you can now manage users and add records to a database. Let's go to the database section and add a new collection. Under the hood PocketBase uses SQLite so a collection is like a new table that will require a couple of fields like title and content for the notes. A database is secure by default which means we'll need to go to the permissions and make all the different operations public. That's all we need for right now.
The New App Router Explained
Let's go ahead and get back into our Next.js code. Currently in the file system you'll see a Pages directory, but go ahead and put that in the garbage because that's the old way of doing things. The new way is to add your routes in the app directory. Next uses file system routing where the names of directories define the actual URL structure of your web app. When a directory is surrounded by brackets that means it's a dynamic route, which might be like an ID or username that can be any wildcard value. In addition, directories can be surrounded by parentheses, which means they'll be ignored by the routing system. This can be useful when you want to place some files somewhere but don't want it to affect the actual URL structure.
Now to build out the UI there's a variety of different reserved file names and the most common one that you'll use is page. It exports a default React component that defines the actual UI that you want to display here. That should give us a basic hello world. Let's go ahead and save the file and run the application. Before we do that though, you should know that Vercel, the company that maintains Next.js, also just released a brand new build tool called Turbo Pack. It's currently in alpha but I like to live dangerously. Go into the package.json and add the turbo flag to the next dev command. Now in the terminal run npm run dev and you should get that hello world in the browser.
Layouts and Global Styling
What you'll notice is that it automatically detected that we were missing a layout.js file, which is the UI that surrounds the entire application. If we open it up, you can see it's a React component that takes children and renders them inside the body of the HTML document. Any code you define here will be displayed on every single page, so it might be a good place to add a global nav bar and footer.
What's cool about layouts is that they can be nested. When you have a layout in a subdirectory, it will only be applied to the children of that route. And you can also fetch data in layouts which makes the application much more efficient because you don't need to re-render and re-fetch data on the sub-routes.
Next thing I'm doing is adding a global CSS file. I'm not going to explain the CSS in this tutorial but you can copy it from the source code which is on GitHub.
Fetching & Displaying Data in Server Components
Now it's time to start thinking about routes. We can create a route called notes by adding a new directory. This route will fetch all the notes from PocketBase and also provide a form to create a new note. First to build out this UI we'll add a page.tsx file, inside of which exports a default React component that will also be marked async.
The cool thing about Next 13 is that components are server components by default, which means they get rendered on the server and we can do data fetching directly inside of them with async await. Define an async function called getNotes that uses the fetch API to retrieve data from your backend. In our case, PocketBase comes with a built-in rest API where we can point to the notes collection and then retrieve a paginated list of results. We'll say page 1 with 30 results per page. We can then convert that result into JSON and then return the items which will be an array of the data in the database.
Now in the component to fetch data, all we have to do is await a call to this function. That gives us access to an array of notes which we can loop over in the UI to render out a basic note component that contains the title, content, and so on. Go ahead and view the app in the browser and you should now see a list of all the records you've created in the database.
Controlling Data Caching Behavior
It's all server rendered, however one important thing to understand is that Next will automatically cache this route because the route segment is not dynamic. In other words, it's treated like a static page. But we can go back into our code and change that by adding the cache no-store option to fetch and now it will re-fetch the items from the server on every request. If you've used Next.js in the past this is roughly the equivalent to getServerSideProps.
Now I want to point out that you don't have to use the Fetch API here. PocketBase has its own SDK that works like an ORM where we could just make a reference to it and then grab all the records by saying db.records.getList('notes'). But now you might be wondering how do we change the caching behavior? Next 13 also has a variety of variables that you can export from a page to change things like the caching behavior and runtime, which is necessary if you are not using fetch.
Creating Dynamic Routes
So that takes care of our list page. Now I want to show you how to create a dynamic route. You'll notice in the Note component that I'm linking to a note with a random ID, which is generated by PocketBase. To create a dynamic route like that we'll add a second directory called [id] surrounded by brackets. That tells Next that this route segment is a wildcard and can be any value. Like before we create a page component and export a server component from it. Let's also create a data fetching function called getNote that retrieves an individual item from the database. And in order to do that it will need the ID from the URL. But let's once again use the fetch API and interpolate the note ID into the actual route.
ISR, Loading, and Error States
Now I want to point out that because this is a dynamic route, it won't automatically cache every request. However, you may want to update the caching behavior. You can implement incremental static regeneration by adding the revalidate option to fetch. This tells Next to regenerate the page on the server if it's older than a certain number of seconds. And if you want to pre-render these pages, you can also export a function called generateStaticParams. This is the equivalent to getStaticPaths in previous versions of Next.
Now in the component we can call this function and pass the params ID from the URL as the argument. That gets the data from the database which we can then display in the UI. At this point you should be able to go to the list of notes, click on one of them, which navigates you to a page just for that one note. That's pretty cool but Next 13 also has a couple of other tricks up its sleeve to manage the loading state and UI between routes. You can add a loading.tsx file inside the ID directory. It simply exports a component with some kind of loading indicator that will be rendered in place of the page when the data is being fetched. This results in faster load times because the content can be streamed directly into that component instead of waiting for the entire page to load. On a similar note, you can also have an error file that will also be rendered in place of the page when an error occurs. And that just makes it really easy to handle loading and error states.
Client Components & Data Mutation
So far in this tutorial we've only looked at data fetching, but your app will also likely need to write or mutate data as well. In order to handle that we'll need an interactive component that's rendered on the client. In the notes directory let's go ahead and create a new component called CreateNote, but unlike our other components this one has use client at the top. This tells Next not to render it on the server, rather only in the browser.
In the components I'm adding the useState hook to add fields for title and content. Then in the JSX I create a form that adds a couple of inputs to modify those values. Each input listens to the onChange event then update the state accordingly. Now let's define a create function that's called when the form is submitted. It will also use fetch to make a request to the PocketBase API, however this time it's a POST request and sends the data from the form as the body of the request.
Refreshing Data and Conclusion
Now let's go ahead and take this client component and declare it in the notes page. If we open up the app we can now fill out this form and create a new item in PocketBase. However, in order to see that item in the list we need to refresh the entire page, and that's not ideal. Luckily the new Next router has us covered. If we go back to the client component we can import the router from next/navigation and it has a special method called refresh. After the new record is created in the database, it'll re-execute the query on the notes page to get the list of notes, which means you will automatically see the new note updated in the list without a full page refresh.
Congratulations you just built a full stack application with Next.js 13. As of today Next 13 is in beta and still a little rough around the edges but I will keep you updated as things change so make sure to subscribe to the channel. Thanks for watching and I will see you in the next one.