Closed17

Next.js tutorial

shusannshusann

Chapter 1

Clone the starter repository

  • written in ts
  • refrects modern web development landscape
npm i
npm run dev
shusannshusann

Chapter 2

global.css: responsible for global styling. it is intended to be imported in layout.tsx file.

if global.css contains Tailwind configuration. Styles can be applied through classname from anywhere withtout namespace collision.

Using the clsx library to toggle class names

styles can be toggled based on property using clsx

    <span
      className={clsx(
        'inline-flex items-center rounded-full px-2 py-1 text-xs',
        {
          'bg-gray-100 text-gray-500': status === 'pending',
          'bg-green-500 text-white': status === 'paid',
        },
      )}
    >
shusannshusann

Chapter 3

Font

Use of next/font/google prevents from Cumulative Layout Shift when a page fully loaded.

/app/ui/fonts.ts

import { Inter, Lusitana } from 'next/font/google'

export const inter = Inter({ subsets: ['latin']})
export const lusitana = Lusitana({ weight: ["400", "700"], subsets: ['latin']})

Image

next/image optimizes related to image like generating multiple size, use modern compressing mothod on my behalf. Just replace img component with Image component.

shusannshusann

Chapter 4: Creating Layouts and Pages

Router

page router are based on the directories and can be routed when exported Page function exists under the app dir.

Layout

partial rendering, that renders only a child component, and shared styling can be done by exporting Layout func.

shusannshusann

Chapter 5: Navigating Between Pages

Next provide Link component alternates <a> tag. Link not only routes users to destination, but also prefetches when appears on the page backgroud and navigate there instantly when pressed.

It's a React component enables client side transitions.

Also, usePathname from next/navigation returns current page's path that is used to to toggle button designs using clsx for example.

shusannshusann

Chapter 6: Setting Up Your Database

This chapter provides the instruction to populate postgresdb on vercel and seed with initial data.

shusannshusann

Chapter 7: Fetching Data

Using React server component to interact with DB that makes securely without requiring additional API layer because it won't expose secrets to clients.

shusannshusann

Chapter 8: Static and Dynamic Rendering

Static rendering is preferred over Dynamic rendering when fulfill the following situation

  • Data is less updated and no frequent udpate required
  • Data is common for all users

In contrast, the situation like user dashboard, the latter rendering option is preferred.

In Next.js, function results are cached by default and can be invalidate using unstable_noStore.

import { unstable_noStore as noStore } from 'next/cache';
shusannshusann

Chapter 9: Streaming

There is a case data fetch blocks a hole page rendered on dynamic rendering.

Use streaming to render the part of the page to be rendered when data is ready.

There are two way to do, loading.tsx and <Suspense>.

loading.tsx

Show skelton components which embed as static component until all dynamic rendering done in the page.tsx

import DashboardSkeleton from "../../ui/skeletons"

export default function Loading() {
  return <DashboardSkeleton />
}

Suspense

To give more granular control on streaming by component level. Use Suspense which is a component of react framework. It is preferred over using loading.tsx.

import { Suspense } from "react";
import { SlowComponent } from "../ui/someComponents";

export default async function Page() {
  return (
    <main>
      <h1>Page</h1>
      <div>
        <Suspense fallback={<h1>loading</h1>}>
          <SlowComponent />
        </Suspense>
      </div>
    </main>
  );
}
shusannshusann

Chapter 10: Partial Prerendering (Optional)

Almost all pages have partial part and dynamic part. Next.js 14 introduced the experimental feature called Partial Prerendering which enables this idea by using Suspense as placeholder until dynamic data being loaded.

shusannshusann

Chapter 11: Adding Search and Pagination

Navigate user with queried page when they put it in search box, onChange event in the input tag will invoke this. Use Debounce is a best practice to reduce DB load unless number of requests will grow extremetely.

The list of benefits using URL search params have several benefits can be listed below.

  • Bookmarkable and Shareable URLs
  • Server-Side Rendering and Initial Load
  • Analytics and Tracking

Use useRouter's replace function to navigate to the page. The URL parameter is updated so user can share and bookmark it.

  const handleSearch = useDebouncedCallback((term: string) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", "1");
    if (term) {
      params.set("query", term);
    } else {
      params.delete("query");
    }
    replace(`${pathname}?${params.toString()}`);
  }, 500);

Pagination

Adding pagination allows user to fetch limited number of items queued to backend services to control demand. Embedding href in page buttons are shown in this tutorial.

  const createPageURL = (pageNumber: number | string) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", pageNumber.toString());
    return `${pathname}?${params.toString()}`;
  };

        <div className="flex -space-x-px">
          {allPages.map((page, index) => {
            let position: "first" | "last" | "single" | "middle" | undefined;

            if (index === 0) position = "first";
            if (index === allPages.length - 1) position = "last";
            if (allPages.length === 1) position = "single";
            if (page === "...") position = "middle";

            return (
              <PaginationNumber
                key={page}
                href={createPageURL(page)}
                page={page}
                position={position}
                isActive={currentPage === page}
              />
            );
          })}
        </div>
shusannshusann

Chapter 12: Mutating Data

  • React's Server Action
  • FormData vlidation and parsing
  • Revalidate and Redirect
  • Dynamic routing

Server Action

Mutate data when form is submitted without creating API endpoint. React creates POST API behind the scence with many security capability.

// Server Component
export default function Page() {
  // Action
  async function create(formData: FormData) {
    'use server';
 
    // Logic to mutate data...
  }
 
  // Invoke the action using the "action" attribute
  return <form action={create}>...</form>;
}

"use server"; directive informs React that is a Server Action.

https://react.dev/reference/react/use-server

Data validation and parsing

https://zod.dev/

Create a z.object

const FormSchema = z.object({
  id: z.string(),
  customerId: z.string(),
  amount: z.coerce.number(),
  status: z.enum(["pending", "paid"]),
  date: z.string(),
});

const CreateInvoice = FormSchema.omit({ id: true, date: true });

Parse the data from FormData

  const { customerId, amount, status } = CreateInvoice.parse({
    customerId: formData.get("customerId"),
    amount: formData.get("amount"),
    status: formData.get("status"),
  });

Revalidate and Redirect

https://nextjs.org/docs/app/api-reference/functions/revalidatePath
https://nextjs.org/docs/app/api-reference/functions/redirect

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export async function createInvoice(formData: FormData) {
  // do something

  revalidatePath("/dashborad/invoices");
  redirect("/dashboard/invoices");
}

### Dynamic routing

https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes

Accomplished by creating a directory surrounded with bracket `[]`; `[id]`, `[post]`, `[slug]`.

### Passing argument to Server Action

```tsx
// Passing an id as argument won't work
<form action={updateInvoice(id)}>
  const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);
  <form action={updateInvoiceWithId} />

Further reading

https://nextjs.org/docs/app/building-your-application/caching#router-cache
https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#1-prefetching
https://nextjs.org/blog/security-nextjs-server-components-actions

shusannshusann

Chapter 13: Handling Error

  • Handle unexptected error, and show fallback UI to the user.
  • Use notFound function and not-found file to handle 404 errors.

Use try-catch syntax and error page

Throw error when unexpected error occurs.
The error thrown by components are handled with the framework, configured error page rendred if error.tsx present in the route as handler.

try {
  doSomething();
} catch(error) {
  throw new Error("Failed to doSomething!");
}

The type of Error is a JavaScript object that passed into the error component.

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string};
  reset: () => void;
}) {
  useEffect(() => {
    console.log(error);
    }, [error]);
  
  return (
    // render something and call reset();
  );
}

Use reset function to delegate framework to render root element.

notFound function and not-found page

import { notFound } from 'next/navigation';

notFound();

Use notFound() function and place not-found.tsx to show it to the user.

Further Reading

shusannshusann

Chapter 14: Improving Accessibility

Improve Accesibility

eslint-plugin-jsx-a11y is a linter validate site a11y by configuring package.json and run npm run lint.

{
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "start": "next start",
    "seed": "node -r dotenv/config ./scripts/seed.js",
+   "lint": "next lint"
  }
}

Implement server-side form validation

Validate input data through form on server side component.

  1. Pass a state variable defined in local component and serverside function into useFormState
  2. Pass handler function from useFormState function to action attribute in a form
  3. Dispatched logic runs on server-side and should return error through defined State type in the lib
  4. Use aria-describedby for a11y and switch visibility using ternary operator

Form data validation done by zod.

shusannshusann

Chapter 15: Adding Authentication

Authentication: Verify the user who they are. (Differ from Authorization)

  1. Create Login route
  2. Add middleware to route login page safely
  3. Add server component to execute authenticate function from form which declared with use client directive
export default function LoginForm() {
  const [errorMessage, dispatch] = useFormState(authenticate, undefined);

  return (
    <form action={dispatch}>
        // form input
    </form>
}

Password hashing is done by using bcrypt in this example. However, external credentials provider like Github, Gmail are preferred over than implementing user/password model manually.

shusannshusann

Chapter 16: Adding Metadata

Add metadata to help improve SEO. Graphical images shown up as an embedding in SNS posts, fields gives great chance to attract users by instrumenting open-graph image.

import { Metadata } from "next";

export const metadata: Metadata = {
  // configurations for Metadata through next API
}
このスクラップは2023/12/24にクローズされました