Học viên: Phạm Thanh Huấn
Lớp: Web Frontend - React Js 29
Emai: HuanPT99@gmail.com
Link tham khảo: https://nextjs.org/blog/next-13-4

Next.js 13.4 một phiên bản cơ bản, đánh dấu sự ổn định cho App Router.

  • App Router (Stable):
    • React Server Components
    • Nested Routes & Layouts
    • Simplified Data Fetching (Lấy dữ liệu đơn giản hơn)
    • Streaming & Suspense (Phát trực tiếp và Suspense)
    • Built-in SEO Support (Hỗ trợ SEO tích hợp sẵn)
  • Turbopack (Beta): Máy chủ chạy trên localhost của bạn, nhanh hơn và độ ổn định được cải thiện.
  • Server Actions (Alpha): Thay đổi dữ liệu trên máy chủ mà không cần JavaScript trên client

Kể từ khi phát hành Next.js 13 cách đây 6 tháng, chúng tôi đã tập trung vào việc xây dựng nền tảng cho tương lai của Next.js — App Router — áp dụng theo từng bước một mà không có những thay đổi không cần thiết.

Hôm nay (05/05), với việc phát hành bản 13.4, giờ đây bạn có thể bắt đầu sử dụng App Router cho sản phẩm của mình.

npm i next@latest react@latest react-dom@latest eslint-config-next@latest

Next.js App Router

Chúng tôi đã phát hành Next.js vào năm 2016 để cung cấp một cách dễ dàng cho các ứng dụng React render phía máy chủ, với mục tiêu của chúng tôi là tạo ra một trang web linh hoạt, được cá nhân hóa và toàn cầu hơn.

Trong bài đăng thông báo ban đầu, chúng tôi đã chia sẻ một số nguyên tắc thiết kế của Next.js:

  • Không cần cài đặt. Sử dụng hệ thống tệp tin như một API
  • Chỉ có JavaScript. Tất cả đều là một hàm
  • Tự động render trên máy chủ và chia nhỏ mã nguồn
  • Việc lấy dữ liệu tùy thuộc vào developer

Next.js hiện đã được sáu tuổi. Các nguyên tắc thiết kế ban đầu của chúng tôi vẫn được giữ nguyên — và khi Next.js đã được áp dụng nhiều hơn bởi developers và các công ty, chúng tôi đang nỗ lực nâng cấp nền tảng để đạt được các nguyên tắc này tốt hơn.

Chúng tôi đang làm việc trên thế hệ tiếp theo của Next.js và hôm nay với phiên bản 13.4, thế hệ tiếp theo này đã ổn định và sẵn sàng để sử dụng. Bài viết này sẽ chia sẻ thêm về các quyết định và lựa chọn thiết kế cho App Router của chúng tôi.

Không cần cài đặt. Sử dụng hệ thống tệp tin như một API

Routing dựa trên hệ thống tệp tin đã trở thành một tính năng cốt lõi của Next.js. Trong bài viết ban đầu của chúng tôi, chúng tôi đã trình bày ví dụ này về cách tạo tuyến đường từ một thành phần React duy nhất:

// Pages Router
// pages/about.js

import React from 'react';
export default () => <h1>About us</h1>;

Không cần phải cấu hình thêm. Chỉ cần thả một tệp tin thư mục pages/ và bộ định tuyến Next.js sẽ lo phần còn lại. Chúng tôi vẫn thích sự đơn giản này với routing. Nhưng khi việc sử dụng framework tăng lên, thì các developers cúng muốn xây dựng các giao diện khác nhau với sự linh hoạt hơn.

Các developers đã yêu cầu hỗ trợ cải thiện việc xác định layouts (bố cục), nhúng các thành phần giao diện(UI) như layouts và linh hoạt hơn trong việc xác định trạng thái tải và lỗi. Đây không phải là điều dễ dàng để trang bị thêm vào bộ định tuyến Next.js hiện có.

Mọi phần của framework phải được thiết kế xung quanh bộ định tuyến. Chuyển trang, tìm nạp dữ liệu, lưu vào bộ nhớ đệm, thay đổi và xác thực lại dữ liệu, phát trực tuyến, định dạng nội dung, v.v.

Để làm cho bộ định tuyến của chúng tôi tương thích với phát trực tuyến và giải quyết các yêu cầu này để hỗ trợ tốt hơn cho các layouts, chúng tôi bắt đầu xây dựng một phiên bản mới của bộ định tuyến.

Đây là kết quả sau khi chúng tôi phát hành lần đầu Layouts RFC (Request for Comment) của chúng tôi.

// New: App Router ✨
// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/page.js
export default function Page() {
  return <h1>Hello, Next.js!</h1>;
}

Quan trọng hơn những gì bạn thấy ở đây là những gì bạn không thấy. Bộ định tuyến mới này (có thể được áp dụng từng phần thông qua thư mục app/) có kiến ​​trúc hoàn toàn khác, được xây dựng trên nền tảng của React Server Component và Suspense.

Nền tảng này đã cho phép chúng tôi loại bỏ các API cụ thể cho Next.js ban đầu được phát triển để mở rộng các nguyên tắc cơ bản React. Ví dụ: bạn không còn phải sử dụng tệp _app tùy chỉnh nữa để tùy chỉnh layouts chung toàn cục:

// Pages Router
// pages/_app.js

// "global layout" này bao quanh tất cả các đường dẫn. 
// Không có cách nào để kết hợp các thành phần bố cục khác
// và bạn không thể lấy dữ liệu toàn cầu từ tệp này.
// data from this file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

Với Pages Router, việc kết hợp layouts không thể được thực hiện và việc lấy dữ liệu không thể được đặt cùng với component. Với App Router mới, điều này đã được hỗ trợ.

// New: App Router ✨
// app/layout.js
//
// Bố cục gốc được chia sẻ cho toàn bộ ứng dụng
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/dashboard/layout.js
//
// Các bố cục có thể được lồng nhau và kết hợp.
export default function DashboardLayout({ children }) {
  return (
    <section>
      <h1>Dashboard</h1>
      {children}
    </section>
  );
}

Với Pages Router, _document được sử dụng để tùy chỉnh payload ban đầu từ máy chủ.

// Pages Router
// pages/_document.js


// Tệp này cho phép bạn tùy chỉnh các thẻ <html> và <body> cho yêu cầu từ máy chủ,
// nhưng thay vì viết các phần tử HTML, nó thêm các tính năng cụ thể cho framework.
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Với App Router, bạn không cần phải nhập , và từ Next.js nữa. Thay vào đó, bạn chỉ cần sử dụng React.

// New: App Router ✨
// app/layout.js
//
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Việc xây dựng một bộ định tuyến hệ thống tệp tin mới cũng là thời điểm thích hợp để giải quyết nhiều yêu cầu tính năng liên quan khác liên quan quan đến hệ thống định tuyến của chúng tôi. Ví dụ:

  • Trước đây, bạn chỉ có thể import global stylesheets từ các gói npm bên ngoài (giống component libraries) trong _app.js. Đây là một trải nghiệm không lý tưởng dành cho developer. Với App Router, bạn có import(và định vị) bất kỳ tệp CSS nào trong bất kỳ component nào.

  • Trước đây, việc kích hoạt kết xuất phía máy chủ với Next.js (thông qua getServerSideProps) có nghĩa là tương tác với ứng dụng của bạn bị chặn cho đến khi toàn bộ trang web được kích hoạt. Với App Router, chúng tôi đã tái cấu trúc kiến ​​trúc để tích hợp sâu với React Suspense, nghĩa là chúng tôi có thể kích hoạt lựa chọn một phần của trang mà không chặn các thành phần khác trong giao diện người dùng khỏi việc tương tác. Nội dung có thể được truyền trực tiếp ngay lập tức từ máy chủ, cải thiện hiệu suất tải của một trang.

Bộ định tuyến là cốt lõi giúp Next.js hoạt động. Nhưng vấn đề không phải là về bản thân bộ định tuyến, mà là cách nó tích hợp các phần còn lại của framework — chẳng hạn như tìm nạp dữ liệu.

Chỉ có JavaScript. Tất cả đều là một hàm

Các nhà phát triển Next.js và React muốn viết mã JavaScript và TypeScript và kết hợp các component cùng nhau. Từ bài viết ban đầu của chúng tôi:

import React from 'react';
import Head from 'next/head';

export default () => (
  <div>
    <Head>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
    </Head>
    <h1>Hi. I'm mobile-ready!</h1>
  </div>
);

Trong các phiên bản Next.js trong tương lai, chúng tôi đã thêm một cải tiến DX để tự động nhập React cho bạn.

Component này bao gồm logic có thể được tái sử dụng và kết hợp ở bất kỳ đâu trong ứng dụng của bạn. Kết hợp với việc định tuyến dựa trên hệ thống tệp tin, điều này đồng nghĩa với một cách dễ dàng để bắt đầu xây dựng các ứng dụng React giống như viết JavaScript và HTML.

Ví dụ: nếu bạn muốn tìm nạp một số dữ liệu, phiên bản ban đầu của Next.js trông như thế này:

import React from 'react';
import 'isomorphic-fetch';

export default class extends React.Component {
  static async getInitialProps() {
    const res = await fetch('https://api.company.com/user/123');
    const data = await res.json();
    return { username: data.profile.username };
  }
}

Trong các phiên bản Next.js tương lai, chúng tôi đã thêm một cải tiến DX (Developer Experience) bằng cách polyfill fetch để bạn không cần phải nhập isomorphic-fetch hoặc node-fetch và có thể sử dụng Web fetch API trên cả máy khách và máy chủ.

Khi dự án phát triển và framework tăng lên, chúng tôi đã khám phá các mẫu mới cho việc lấy dữ liệu.

getInitialProps chạy cả trên máy chủ và máy khách. API này đã mở rộng thành phần React, cho phép tạo một Promise và chuyển kết quả tới các props của component.

Trong khi getInitialProps vẫn hoạt động cho đến nay, chúng tôi tiếp tục phát triển các API lấy dữ liệu thế hệ tiếp theo dựa trên phản hồi của khách hàng: getServerSideProps và getStaticProps.

// Generate a static version of the route
export async function getStaticProps(context) {
  return { props: {} };
}
// Or dynamically server-render the route
export async function getServerSideProps(context) {
  return { props: {} };
}

Những API này làm cho việc code của bạn chạy rõ ràng hơn, có thể là trên máy khách hoặc máy chủ và cho phép tối ưu hóa tĩnh tự động cho ứng dụng Next.js. Hơn nữa, nó cho phép xuất tĩnh, cho phép Next.js được triển khai ở những nơi không hỗ trợ máy chủ (ví dụ: AWS S3 bucket).

Tuy nhiên, đây không chỉ đơn giản là “JavaScript” và chúng tôi muốn tuân thủ chặt chẽ hơn nguyên tắc thiết kế ban đầu của chúng tôi.

Kể từ khi Next.js được tạo, chúng tôi đã hợp tác chặt chẽ với nhóm React core tại Meta để xây dựng các tính năng framework dựa trên các nguyên tắc cơ bản của React. Sự hợp tác của chúng tôi, kết hợp với nhiều năm nghiên cứu và phát triển từ nhóm React core, đã mang đến cơ hội cho Next.js đạt được mục tiêu của chúng tôi thông qua phiên bản mới nhất của React, bao gồm Server Component.

Với App Router, bạn lấy dữ liệu bằng cách sử dụng cú pháp async và await quen thuộc. Không có API mới nào để tìm hiểu. Theo mặc định, tất cả các components đều là React Server Component, vì vậy quá trình tìm nạp dữ liệu diễn ra an toàn trên máy chủ. Ví dụ:

// app/page.js

export default async function Page() {
  const res = await fetch('https://api.example.com/...');
  // The return value is *not* serialized
  // You can use Date, Map, Set, etc.
  const data = res.json();

  return '...';
}

Điều quan trọng là nguyên tắc "việc lấy dữ liệu do nhà phát triển quyết định”. Bạn có thể lấy dữ liệu và kết hợp bất kỳ component nào. Và không chỉ các components do Next.js tạo ra, mà bất kỳ component nào trong hệ sinh thái Server Components, chẳng hạn như Twitter embed react-tweet, đã được thiết kế để tích hợp với Server Components và chạy hoàn toàn trên máy chủ.

// app/page.js

import { Tweet } from 'react-tweet';

export default async function Page() {
  return <Tweet id="790942692909916160" />;
}

Vì Router được tích hợp với React Suspense nên bạn có thể hiển thị nội dung dự phòng một cách mượt mà trong khi các phần nội dung của bạn đang tải và từ từ hiển thị nội dung như mong muốn.

// app/page.js

import { Suspense } from 'react';
import { PostFeed, Weather } from './components';

export default function Page() {
  return (
    <section>
      <Suspense fallback={<p>Loading feed...</p>}>
        <PostFeed />
      </Suspense>
      <Suspense fallback={<p>Loading weather...</p>}>
        <Weather />
      </Suspense>
    </section>
  );
}

Hơn nữa, router đánh dấu các điều hướng trang như chuyển tiếp, cho phép chuyển tiếp từ route có thể bị gián đoạn.

Tự động render trên máy chủ và chia nhỏ mã nguồn

Khi chúng tôi tạo Next.js, việc cấu hình webpack, babel và các công cụ khác một cách thủ công để chạy một ứng dụng React vẫn còn phổ biến. Việc thêm các tối ưu hóa khác như server rendering hoặc code splitting thường không được triển khai trong các giải pháp tùy chỉnh. Next.js, cũng như các framework React khác, tạo một lớp trừu tượng để triển khai và áp đặt những quy ước tốt nhất này.

Code splitting dựa trên route đồng nghĩa với việc mỗi tệp trong thư mục pages/ của bạn sẽ được phân tách thành các gói JavaScript riêng biệt, giúp giảm tải hệ thống tệp tin và cải thiện hiệu suất tải trang ban đầu.

Điều này có lợi cho cả các ứng dụng được render trên máy chủ và các ứng dụng một trang với Next.js, vì trang sau thường tải một gói JavaScript lớn duy nhất khi khởi động ứng dụng. Tuy nhiên, để triển khai phân tách mã cấp thành phần, các nhà phát triển cần sử dụng next/dynamic để nhập component một cách động.

// app/page.tsx

import dynamic from 'next/dynamic';

const DynamicHeader = dynamic(() => import('../components/header'), {
  loading: () => <p>Loading...</p>,
});

export default function Home() {
  return <DynamicHeader />;
}

Với App Router, các Server Components không được bao gồm trong gói JavaScript dành cho trình duyệt. Các thành phần máy khách được mặc định tự động code split (sử dụng webpack hoặc Turbopack trong Next.js). vì kiến trúc router toàn bộ hỗ trợ streaming và Suspense, bạn có thể từ từ gửi từ phần của giao diện người dùng từ máy chủ đến máy khách.

Ví dụ: bạn có thể code split các luồng mã với logic có điều kiện. Trong ví dụ này, bạn sẽ không cần tải JavaScript phía máy khách của bảng điều khiển cho người dùng chưa đăng nhập.

// app/layout.tsx

import { getUser } from './auth';
import { Dashboard, Landing } from './components';

export default async function Layout() {
  const isLoggedIn = await getUser();
  return isLoggedIn ? <Dashboard /> : <Landing />;
}

Turbopack (Thử nghiệm)

Turbopack, trình đóng gói mới mà chúng tôi đang thử nghiệm và ổn định trong Next.js, giúp tăng tốc các lần lặp cục bộ trong khi làm việc trên ứng dụng Next.js của bạn (thông qua next dev --turbo) và ngay sau đó là các phiên bản sản xuất của bạn (next build --turbo)

Kể từ khi phát hành bản alpha trong Next.js 13, chúng tôi đã chứng kiến ​​sự tăng trưởng ổn định trong việc sử dụng khi chúng tôi nỗ lực khắc phục các lỗi và hỗ trợ cho các tính năng còn thiếu. Chúng tôi đã sử dụng Turbopack trên Vercel.com và với nhiều khách hàng của Vercel đang vận hành các trang web Next.js lớn để thu thập phản hồi và cải thiện tính ổn định. Chúng tôi rất biết ơn sự hỗ trợ của cộng đồng trong việc thử nghiệm và báo cáo lỗi cho nhóm của chúng tôi.

Bây giờ, sau sáu tháng, chúng tôi đã sẵn sàng tiến vào giai đoạn thử nghiệm (beta).

Turbopack chưa có đầy đủ tính năng tương đương với webpack và Next.js. Chúng tôi đang theo dõi việc hỗ trợ cho các tính năng đó trong vấn đề này. Tuy nhiên, phần lớn các trường hợp sử dụng sẽ được hỗ trợ. Mục tiêu của chúng tôi với bản beta này là tiếp tục khắc phục các lỗi còn lại từ việc gia tăng sự ổn định và chuẩn bị cho tính ổn định trong các phiên bản tương lai.

Sự đầu tư của chúng tôi vào việc cải thiện công cụ gia tăng và lớp bộ nhớ đệm của Turbopack sẽ không chỉ giúp tăng tốc độ phát triển cục bộ, mà còn sớm giúp tăng tốc xây dựng sản phẩm. Hãy đón chờ phiên bản Next.js trong tương lai, khi bạn sẽ có thể chạy next build --turbo để có sự xây dựng tức thì.

Hãy dùng thử Turbopack beta ngay hôm nay trong Next.js 13.4 với next dev --turbo.

Server Actions (Alpha)

Hệ sinh thái React đã chứng kiến ​​rất nhiều sự đổi mới và khám phá các ý tưởng xung quanh đến các biểu mẫu, quản lý trạng thái biểu mẫu cũng như lưu vào bộ nhớ đệm và xác thực lại dữ liệu. Theo thời gian, React đã trở nên cụ thể hơn đối với một số mẫu này. Ví dụ: đề xuất “uncontrolled components” cho trạng thái biểu mẫu

Hệ sinh thái của các giải pháp hiện tại đã là các giải pháp có thể tái sử dụng phía máy khách hoặc các nguyên các nguyên tắc cơ bản được tích hợp trong các framework. Cho đến nay, vẫn chưa có cách nào để kết hợp các biến đổi và nguyên tắc dữ liệu phía máy chủ. Nhóm React đã làm việc để tạo ra một giải pháp phía nhà cung cấp dịch vụ đầu tiên cho các biến đổi này.

Chúng tôi vui mừng thông báo hỗ trợ cho các Server Actions thử nghiệm trong Next.js, cho phép bạn thay đổi dữ liệu trên máy chủ, gọi các hàm trực tiếp mà không cần tạo lớp API trung gian.

// app/post/[id]/page.tsx (Server Component)

import kv from './kv';

export default function Page({ params }) {
  async function increment() {
    'use server';
    await kv.incr(`post:id:${params.id}`);
  }

  return (
    <form action={increment}>
      <button type="submit">Like</button>
    </form>
  );
}

Với Server Actions, bạn có thể thực hiện biến đổi mạnh mẽ trên máy chủ, giảm thiểu lượng mã JavaScript phía máy khách hơn và cải thiện dần các biểu mẫu.

// app/dashboard/posts/page.tsx (Server Component)

import db from './db';
import { redirect } from 'next/navigation';

async function create(formData: FormData) {
  'use server';
  const post = await db.post.insert({
    title: formData.get('title'),
    content: formData.get('content'),
  });
  redirect(`/blog/${post.slug}`);
}

export default function Page() {
  return (
    <form action={create}>
      <input type="text" name="title" />
      <textarea name="content" />
      <button type="submit">Submit</button>
    </form>
  );
}

Server Actions trong Next.js đã được thiết kế để tích hợp sâu vào toàn bộ vòng đời của dữ liệu, bao gồm Next.js cache, Incremental Static Regeneration (ISR) và bộ định tuyến máy khách

Việc xác thực lại dữ liệu thông qua các API mới revalidatePath và revalidateTag đảm bảo rằng việc thay đổi, render lại trang hoặc chuyển hướng có thể xảy ra trong một vòng tròn mạng duy nhất, đảm bảo dữ liệu chính xác được hiển thị trên máy khách, ngay cả khi nhà cung cấp nguồn dữ liệu chậm

// app/dashboard/posts/page.tsx (Server Component)

import db from './db';
import { revalidateTag } from 'next/cache';

async function update(formData: FormData) {
  'use server';
  await db.post.update({
    title: formData.get('title'),
  });
  revalidateTag('posts');
}

export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['posts'] } });
  const data = await res.json();
  // ...
}

Server Actions được thiết kế để có thể kết hợp. Bất kỳ ai trong cộng đồng React đều có thể xây dựng và phát hành Server Actions và phân phối chúng trong hệ sinh thái. Giống như các Server Components, chúng tôi rất vui mừng về thời kỳ mới của các nguyên tắc cơ bản có thể kết hợp được cho cả máy khách và máy chủ

Server Actions có sẵn trong phiên bản alpha với Next.js 13.4. Bằng cách chọn sử dụng Server Actions, Next.js sẽ sử dụng kênh phát hành thử nghiệm của React

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};

module.exports = nextConfig;

Những cải tiến khác

  • Draft Mode: Truy xuất và hiển thị nội dung nháp từ hệ thống headless CMS của bạn. Draft mode hoạt động cả trong trang và ứng dụng. Chúng tôi đã cải thiện và đơn giản hóa Preview Mode API, API này vẫn hoạt động cho các trang. Preview Mode (Chế độ xem trước) không hoạt động trong ứng dụng — bạn nên sử dụng Draft mode.