Almost all titles have links to the oficial NextJS documentation!
- 01 - Creating a new project
- 02 - NextJS folders Structure
- 03 - NextJS CSS
- 04 - Image optimization
- 05 - Routing & Layout
- 06 - Link component and navigation optimization
- 07 - Server Components to fetch data
- 08 - Requests optimization
- 09 - Rendering Static vs Dynamic
- 10 - Loading.tsx & Skeletons Loading
- 11 - Searching
- 12 - Mutating Data - "CRUD"
- 13 - revalidatePath Fresh Data fetching
- 14 - Errors and NotFound
- 15 - Metadata and SEO
- 16 - Checklist to optimize pages
npx create-next-app@latest --typescript
cd <project-name>
npm install
npm run dev
/app:
Contains all the routes, components, and logic for your application, this is where you'll be mostly working from./app/lib:
Contains functions used in your application, such as reusable utility functions and data fetching functions./app/ui:
Contains all the UI components for your application, such as cards, tables, and forms. To save time, we've pre-styled these components for you./app/ui/fonts.ts
is where you should keep external fonts, by doing that, Next will download them previously to reduce fetching./public:
Contains all the static assets for your application, such as images./scripts:
Contains a seeding script that you'll use to populate your database in a later chapter. Config Files: You'll also notice config files such as next.config.js at the root of your application. Most of these files are created and pre-configured when you start a new project using create-next-app. You will not need to modify them in this course.
global.css
at root layout.tsx
home.module.css
module CSS allow you to scope CSS to a component by automatically creating unique class names, so you don't have to worry about style collisions as well.
Use the <Image/>
component with the width and height equal to the aspect ratio of the image
Each `folder` is a route, inside the folder you need a `page.tsx` which will be the "`index.html`", the `layout.tsx` will be some standard layouts for the page.
<Link />
component works just like the tag <a>
, but with the difference that it prefetches other pages, resulting in a faster navigation between pages due to the background preload
Use async
in the function Page()
and then use an await to fetch some data directly with SQL queries or ORM.
Use
Promise.all([Promise1, Promise2, Promise3])
for parallel data fetching
- What is? Fetches data > render the page > cache it to deliver later on requests > Content Delivery Network (CDN) > clients
- When should be used? When using UI with no data or data that is shared across users
- Pros: Faster Websites, Reduced Server Load, SEO.
- What is? Content is rendered on the server for each user at request time (when the user visits the page)
- Pros: Real-Time Data, User-Specific Content (personalized content based on user interactions), Request Time Information. With dynamic rendering, your application is only as fast as your slowest data fetch.
import { unstable_noStore as noStore } from 'next/cache';
Use the noStore()
inside the server components or inside the async functions
loading.tsx
is a special Next.js file, it show as a replacement while page content loads.- Inside it can be rendered static content like "loading skeletons"
- To avoid the
loading.tsx
rendering in other sub-routes, move thepage.tsx
andloading.tsx
to a folder with(
)
Contents like
CardWrapper
,RevenueChart
andLatestInvoices
are the components with data being fetched, while the data is streaming theSuspense
loads theskeletons
which are just the prototype structures of the UI
- Capture the user's input. With
handleChange
- Update the URL with the search params.
- Keep the URL in sync with the input field.
- Update the table to reflect the search query.
Searching component + sync with field and URL + debouncing optimization
'use client';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { usePathname, useSearchParams, useRouter } from 'next/navigation';
import { useDebouncedCallback } from 'use-debounce';
export default function Search({ placeholder }: { placeholder: string }) {
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();
const handleSearch = useDebouncedCallback((term) => {
console.log(`Searching... ${term}`);
const params = new URLSearchParams(searchParams);
// params.set('page', '1'); pagination
if (term) {
params.set('query', term);
} else {
params.delete('query');
}
replace(`${pathname}?${params.toString()}`);
}, 500);
return (
<div className="relative flex flex-1 flex-shrink-0">
<label htmlFor="search" className="sr-only">
Search
</label>
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
placeholder={placeholder}
onChange={(event) => handleSearch(event.target.value)}
defaultValue={searchParams.get('query')?.toString()}
/>
<MagnifyingGlassIcon className="absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
);
}
Allows fresh data to be fetched from the server.
error.tsx
UI rendered upon any error (catch-all)not-found.tsx
UI served when used the{ notFound } from 'next/navigation';
- Created a database in the same region as your application code to reduce latency between your server and database.
- Fetched data on the server with React Server Components. This allows you to keep expensive data fetches and logic on the server, reduces the client-side JavaScript bundle, and prevents your database secrets from being exposed to the client.
- Used SQL to only fetch the data you needed, reducing the amount of data transferred for each request and the amount of JavaScript needed to transform the data in-memory.
- Parallelize data fetching with JavaScript - where it made sense to do so.
- Implemented Streaming to prevent slow data requests from blocking your whole page, and to allow the user to start interacting with the UI without waiting for everything to load.
- Move data fetching down to the components that need it, thus isolating which parts of your routes should be dynamic in preparation for Partial Prerendering.
- Using Debouncing to prevent triggers in requests to the server everytime something occurs, adding a delay before the next thing, like for each keystroke make a request to query something, adding a debounce, it adds a delay before triggering the query, allowing multiples keystrokes without triggering requests
- Project in GitHub
- Deploy in Vercel
- In Vercel dashboard > "Storage" > Postgres > After creation > tab
.env.local
> "Show Secrets" > "Copy Snippet" - In your project file
.env
paste the secrets copied from the DB - run in project folder
npm i @vercel/postgres
- For "automatic" database seeding checkout
seed.js
file, and insert the the script inpackage.json
>"seed": "node -r dotenv/config ./scripts/seed.js"
then runnpm run seed