Learn Next 14 重點整理

搭配 Chatbun 輔助學習

使用方式:簡單來說,這是基於 next 14 官網資料的 ai 聊天機器人,有想知道的就可以直接問它,但資料量不多,所以需要搭配這篇重點整理去問,例如:

next js 的資料夾結構?
next js 如何處理個人驗證?

截圖 2023-11-10 下午5.00.03

資料夾結構

strutures

  1. /app: 主要的資料夾
  2. /app/lib: 放hooks, utils
  3. /app/ui: UI 元件
  4. /public: 圖片 靜態資源
  5. /scripts/: 資料庫
  6. global.css: reset.css 全域 css 規則
  7. 條件判斷的 css 可用 clsx 套件
  8. 字型 font.ts
  9. 圖片 < Image> 元件
  10. 如果要優化 外部圖片 or 本地字型 看這篇最下面

路由

  1. 巢狀路由

    image.png

  2. 以 page.tsx 為主軸,例如 /dashboard/page.tsx => 頁面 /dashboard

  3. /dashboard 中所有 page 的共用元件,則放在 /dashboard/layout.tsx,dashboard 的子頁面時,共用元件就不需要 re-render (partial-rendering)

  4. < Link> 元件 切換頁面時有 SPA 的效果,而且在載入 Link 元件時,會同步 prefetch 他的對應頁面,達到效能優化的效果。

  5. usePathname 或任何 react 的 hook 都需要在 'use client' 環境使用

  6. 可以使用 clsx 套件做 active 的樣式

'use client';
 
import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import clsx from 'clsx';
 
// ...
 
export default function NavLinks() {
  const pathname = usePathname();
 
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className={clsx(
              'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
              {
                'bg-sky-100 text-blue-600': pathname === link.href,
              },
            )}
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}

fetching data

  1. 使用 React Server Components (server 端接資料),則不需要 API layer
  2. 如果在 client 端皆資料的話,則需要 API layer

在 server 端撈 sql 的資料步驟

/lib/data.ts

export async function fetchRevenue() {
  // Add noStore() here prevent the response from being cached.
  // This is equivalent to in fetch(..., {cache: 'no-store'}).

  try {
    // Artificially delay a response for demo purposes.
    // Don't do this in real life :)

    console.log('Fetching revenue data...');
    await new Promise((resolve) => setTimeout(resolve, 3000));

    const data = await sql<Revenue>`SELECT * FROM revenue`;

    console.log('Data fetch complete after 3 seconds.');

    return data.rows;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch revenue data.');
  }
}

/dashboard/page.tsx

// 因為 Page 元件是非同步函示 所以可以等待資料來源
const revenue = await fetchRevenue();

...

<RevenueChart revenue={revenue}  />

避免 request waterfalls

image.png

可利用 Parallel data fetching 解決上述問題 提升效能

export async function fetchCardData() {
  try {
    const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
    const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
    const invoiceStatusPromise = sql`SELECT
         SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
         SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending"
         FROM invoices`;
 
    const data = await Promise.all([
      invoiceCountPromise,
      customerCountPromise,
      invoiceStatusPromise,
    ]);
    // ...
  }
}

骨架屏

  1. 整頁骨架屏:搭配loading.tsx實作 /dashboard 頁面專屬的骨架屏 (必須放在(overview:檔名隨便)的資料夾中)

    image.png

  2. 元件骨架屏:為了避免 request waterfalls,將元件拆細,便可做到 code split 的效果,並且利用 react Suspense 元件做出骨架屏的效果

<Suspense fallback={<LatestInvoicesSkeleton />}>
  <LatestInvoices />
</Suspense>
  • You could stream the whole page like we did with loading.tsx but that may lead to a longer loading time if one of the components has a slow data fetch.
  • You could stream every component individually but that may lead to UI popping into the screen as it becomes ready.
  • You could also create a staggered effect by streaming page sections. But you'll need to create wrapper components.

利用 URL 實作搜尋功能

  1. usePathname() => 取得全部網址
  2. useSearchParams() => 取得 query
  3. useRouter() => 切換搜尋列表網址並有 SPA 的效果
  4. URLSearchParams => web API
  5. use-debounce => debounce 套件
  6. 利用上述 client 端取得 server 端的參數 去搜尋資料庫中的資料

參考這頁 adding-search-and-pagination

CRUD

利用 React Server Actions 建立方法 createInvoice

React Server Actions 會創建 post 請求的 api 節點,所以我們不需要手動創建

並且利用 zod 檢查表單型別(server端)

  • 利用react server actions 實作新建資料的邏輯
  • 在頁面引入後 利用action的 props 觸發方法
    <form action={createInvoice}>

處理錯誤

  1. 使用 try catch 處理 server 端的錯誤
  2. 使用 error 元件處理所有錯誤頁面(必須是 client component)
  3. 使用 notFound(), not-found 元件處理401錯誤

表單檢查

  • require attr
  • server 端使用 zod 驗證表單

isAuth 檢查

  1. 使用 next-auth
  2. 被保護的路由只有在 middleware 驗證通過才會 render
  3. bcrypt 套件依賴 Node.js (產生密碼用的)

Meta Data

<!--社群軟體分享時會出現的資訊-->
<meta property="og:title" content="Title Here" />
<meta property="og:description" content="Description Here" />
<meta property="og:image" content="image_url_here" />

結論

優點:

  1. 提供了許多好用的 api, component 包含路由、圖片優化等
  2. 活用了 react server component 的用法
  3. 用資料夾來決定路由的方式在小型專案的情況下非常方便
  4. 這次新推出的官方課程算是簡短好上手,相當推薦嘗試看看

缺點:

  1. 雖然狀態管理一樣推薦用 redux,但是 Server Components 是一种新的實驗性功能,需要重新考慮如何管理和共享狀態
  2. 儘管也有管理元件的概念,但”資料夾決定路由“這件事情在大型專案下的維護性值得觀察