Hãy cùng tôi đi sâu vào nghiên cứu điển hình này, nơi chúng ta sẽ bắt đầu hành trình xây dựng một ứng dụng thời gian thực, tận dụng sức mạnh và sự khéo léo của các hành động của máy chủ Next.js. Cho dù bạn là nhà phát triển dày dạn kinh nghiệm hay chỉ mới mạo hiểm bước vào lĩnh vực ứng dụng thời gian thực, luôn có rất nhiều thông tin chi tiết đang chờ bạn.
Để đưa nó vào quan điểm, hãy xem xét một số ví dụ phổ biến:
Ứng dụng nhắn tin tức thời : Các nền tảng như WhatsApp và Telegram nơi tin nhắn được gửi, nhận và xem không chậm trễ.
Công cụ cộng tác : Hãy nghĩ đến Google Tài liệu, nơi nhiều người dùng có thể chỉnh sửa tài liệu đồng thời, quan sát các thay đổi của nhau trong thời gian thực.
Live Stock Tickers : Nền tảng hiển thị giá cổ phiếu cập nhật tức thời với những biến động của thị trường.
Trò chơi nhiều người chơi trực tuyến : Nơi người chơi tương tác với nhau và với môi trường mà không có độ trễ, đảm bảo trải nghiệm chơi trò chơi liền mạch.
Vậy, tại sao chức năng thời gian thực lại được săn đón như vậy?
Việc xây dựng các ứng dụng thời gian thực không phải là không có trở ngại:
Các vấn đề về khả năng mở rộng : Các ứng dụng thời gian thực thường cần xử lý nhiều kết nối đồng thời, yêu cầu cơ sở hạ tầng mạnh mẽ.
Tính toàn vẹn của dữ liệu : Đảm bảo rằng dữ liệu thời gian thực vẫn nhất quán trên các giao diện người dùng khác nhau có thể là một thách thức, đặc biệt là với nhiều lần chỉnh sửa hoặc tương tác đồng thời.
Độ trễ : Một ứng dụng thời gian thực chỉ tốt bằng thành phần chậm nhất của nó. Đảm bảo sự chậm trễ tối thiểu đòi hỏi phải tối ưu hóa cẩn thận và sử dụng tài nguyên hiệu quả.
Các hành động trong hệ sinh thái React, mặc dù vẫn còn đang thử nghiệm, nhưng đã mang lại sự thay đổi mô hình bằng cách cho phép các nhà phát triển thực thi mã không đồng bộ để đáp ứng các tương tác của người dùng.
Thật thú vị, mặc dù chúng không dành riêng cho Next.js hoặc React Server Components, nhưng việc sử dụng chúng thông qua Next.js có nghĩa là bạn đang ở trên kênh thử nghiệm React.
Đối với những người quen thuộc với các biểu mẫu HTML, bạn có thể nhớ lại việc chuyển các URL tới chỗ dựa action
. Giờ đây, với Hành động, bạn có thể chuyển trực tiếp một chức năng, làm cho các tương tác trở nên năng động và tích hợp hơn.
<button action={() => { /* async function logic here */ }}>Click me!</button>
Các hành động biểu mẫu thể hiện sự kết hợp khéo léo giữa các hành động của React với API <form>
tiêu chuẩn. Chúng cộng hưởng với thuộc tính formaction
nguyên thủy trong HTML, giúp các nhà phát triển có thể nâng cao trạng thái tải liên tục và các chức năng khác vượt trội.
<!-- Traditional HTML approach --> <form action="/submit-url"> <!-- form elements --> </form> <!-- With Next.js 13.4 Form Actions --> <form action={asyncFunctionForSubmission}> <!-- form elements --> </form>
Chức năng máy chủ về cơ bản là các chức năng hoạt động ở phía máy chủ nhưng có thể được gọi từ máy khách. Những điều này nâng khả năng kết xuất phía máy chủ của Next.js lên một cấp độ hoàn toàn mới.
Chuyển sang Hành động máy chủ , chúng có thể được hiểu là Chức năng máy chủ, nhưng những chức năng được kích hoạt cụ thể dưới dạng hành động. Sự tích hợp của chúng với các phần tử biểu mẫu, đặc biệt là thông qua chỗ dựa action
, đảm bảo rằng biểu mẫu vẫn tương tác ngay cả trước khi tải JavaScript phía máy khách. Điều này mang lại trải nghiệm người dùng mượt mà hơn, với việc hydrat hóa React không phải là điều kiện tiên quyết để gửi biểu mẫu.
// A simple Server Action in Next.js 13.4 <form action={serverActionFunction}> <!-- form elements --> </form>
Cuối cùng, chúng ta có Server Mutations , là một tập hợp con của Server Actions. Chúng đặc biệt hiệu quả khi bạn cần sửa đổi dữ liệu trên máy chủ và sau đó thực hiện các phản hồi cụ thể, chẳng hạn như redirect
, revalidatePath
hoặc revalidateTag
.
const serverMutationFunction = async () => { // Modify data logic here... // ... return { revalidatePath: '/updated-path' }; } <form action={serverMutationFunction}> <!-- form elements --> </form>
Lưu ý: Tóm lại, khung Hành động máy chủ của Next.js 13.4, được củng cố bởi các Hành động, Hành động biểu mẫu, Chức năng máy chủ và Đột biến máy chủ, là hiện thân của cách tiếp cận chuyển đổi đối với các ứng dụng web thời gian thực.
Khi chúng tôi tiếp tục nghiên cứu trường hợp của mình, bạn sẽ tận mắt chứng kiến sức mạnh mà các tính năng này mang lại. Vì vậy, hãy chuẩn bị cho hành trình thú vị phía trước!
Trước tiên, bạn cần bật Tác vụ máy chủ trong dự án Next.js của mình. Chỉ cần thêm đoạn mã sau vào tệp next.config.js
của bạn:
module.exports = { experimental: { serverActions: true, }, }
Trong các Thành phần Máy chủ : Một Hành động Máy chủ có thể được xác định dễ dàng trong một Thành phần Máy chủ, như sau:
export default function ServerComponent() { async function myAction() { 'use server' // ... } }
Với Thành phần máy khách : Khi sử dụng Hành động máy chủ bên trong Thành phần máy khách, hãy tạo hành động trong một tệp riêng biệt rồi nhập hành động đó.
// app/actions.js 'use server' export async function myAction() { // ... }
// app/client-component.js import { myAction } from './actions' export default function ClientComponent() { return ( <form action={myAction}> <button type="submit">Add to Cart</button> </form> ) }
Gọi tùy chỉnh : Bạn có thể sử dụng các phương thức tùy chỉnh như startTransition
để gọi các Tác vụ máy chủ bên ngoài biểu mẫu, nút hoặc đầu vào.
// Example using startTransition 'use client' import { useTransition } from 'react' import { addItem } from '../actions' function ExampleClientComponent({ id }) { let [isPending, startTransition] = useTransition() return ( <button onClick={() => startTransition(() => addItem(id))}> Add To Cart </button> ) }
Next.js 13.4 cũng cung cấp tính năng Nâng cao lũy tiến, cho phép <form>
hoạt động mà không cần JavaScript. Hành động máy chủ có thể được chuyển trực tiếp đến <form>
, làm cho biểu mẫu tương tác ngay cả khi JavaScript bị tắt.
// app/components/example-client-component.js 'use client' import { handleSubmit } from './actions.js' export default function ExampleClientComponent({ myAction }) { return ( <form action={handleSubmit}> {/* ... */} </form> ) }
Theo mặc định, nội dung yêu cầu tối đa được gửi tới Hành động máy chủ là 1 MB. Nếu cần, bạn có thể định cấu hình giới hạn này bằng tùy chọn serverActionsBodySizeLimit
:
module.exports = { experimental: { serverActions: true, serverActionsBodySizeLimit: '2mb', }, }
npx create-next-app@latest my-real-time-app
Thay my-real-time-app
bằng tên mong muốn cho dự án của bạn. Lệnh này thiết lập một dự án Next.js mới với các cấu hình mặc định.
Với việc giới thiệu Next.js 13.4, Bộ định tuyến ứng dụng là một tính năng quan trọng cho phép các nhà phát triển sử dụng bố cục dùng chung, định tuyến lồng nhau, xử lý lỗi, v.v. Nó được thiết kế để hoạt động cùng với thư mục pages
hiện có, nhưng nó được đặt trong một thư mục mới có tên app
.
Tạo một thư mục app
trong thư mục gốc của dự án của bạn.
Theo mặc định, các thành phần bên trong thư mục app
là Thành phần máy chủ , cung cấp hiệu suất tối ưu và cho phép các nhà phát triển dễ dàng áp dụng chúng.
my-real-time-app/ │ ├── app/ # Main directory for App Router components │ ├── _error.js # Custom error page │ ├── _layout.js # Shared layout for the app │ │ │ ├── dashboard/ # Nested route example │ │ ├── index.js # Dashboard main view │ │ └── settings.js # Dashboard settings view │ │ │ ├── index.js # Landing/Home page │ ├── profile.js # User profile page │ ├── login.js # Login page │ └── register.js # Registration page │ ├── public/ # Static assets go here (images, fonts, etc.) │ ├── images/ │ └── favicon.ico │ ├── styles/ # Global styles or variables │ └── global.css │ ├── package.json # Dependencies and scripts ├── next.config.js # Next.js configuration └── README.md # Project documentation
Thành phần máy chủ : Lý tưởng cho các phần không tương tác trong ứng dụng của bạn. Các thành phần này được hiển thị trên máy chủ và được gửi tới máy khách dưới dạng HTML. Ưu điểm ở đây là cải thiện hiệu suất, giảm JavaScript phía máy khách và khả năng tìm nạp dữ liệu hoặc truy cập trực tiếp vào tài nguyên phụ trợ.
Thành phần máy khách : Được sử dụng cho các thành phần giao diện người dùng tương tác. Chúng được kết xuất trước trên máy chủ và sau đó được "ngậm nước" trên máy khách để thêm tính tương tác.
Để phân biệt giữa các thành phần này, Next.js đã giới thiệu chỉ thị "use client"
. Lệnh này chỉ ra rằng một thành phần phải được coi là Thành phần máy khách. Nó phải được đặt ở đầu tệp thành phần, trước bất kỳ lần nhập nào.
Ví dụ: nếu bạn có một bộ đếm tương tác, như trong mã được cung cấp, thì bạn sẽ sử dụng lệnh "use client"
để cho biết rằng đó là một thành phần phía máy khách.
Sử dụng Cấu phần máy chủ theo mặc định (vì chúng nằm trong thư mục app
).
Chỉ chọn Thành phần ứng dụng khách khi bạn có các trường hợp sử dụng cụ thể như thêm tính tương tác, sử dụng API chỉ dành cho trình duyệt hoặc tận dụng các hook React phụ thuộc vào trạng thái hoặc chức năng của trình duyệt.
Lưu ý: Theo cấu trúc và thiết lập này, bạn sẽ tiếp tục xây dựng một ứng dụng thời gian thực hoạt động hiệu quả với Server Actions của Next.js 13.4.
Sức mạnh của Next.js 13.4 tỏa sáng khi tích hợp các chức năng phụ trợ thời gian thực vào dự án của chúng tôi. Hãy xem qua các bước với các ví dụ mã có liên quan cho my-real-time-app
.
Đối với my-real-time-app
, các hành động của máy chủ đóng vai trò là cầu nối chính giữa giao diện người dùng và phụ trợ , cho phép các giao dịch dữ liệu hiệu quả mà không cần API riêng biệt.
// my-real-time-app/app/actions/index.js export * from './auth-action'; export * from './chat-action';
Trong my-real-time-app
, chúng tôi tận dụng các hành động của máy chủ để hợp lý hóa quy trình xác thực.
// my-real-time-app/app/actions/auth-action.js export const login = async (credentials) => { // Logic for authenticating user with credentials // Return user details or error message }; export const logout = async (userId) => { // Logic for logging out the user // Return success or error message }; export const register = async (userInfo) => { // Logic for registering a new user // Store user in database and return success or error message };
// my-real-time-app/app/actions/chat-action.js export const sendMessage = async (messageDetails) => { // Logic to send a new message // Store message in database and inform other users via WebSocket or similar }; export const receiveMessage = async () => { // Logic to receive a message in real-time // Return the message details }; export const getRecentMessages = async (userId) => { // Logic to fetch recent messages for the user // Retrieve messages from the database };
// Initialize MongoDB connection const { MongoClient } = require('mongodb'); const client = new MongoClient(process.env.MONGODB_URI); await client.connect(); // Now, use this connection in server actions to interact with the database.
// my-real-time-app/app/actions/chat-action.js export const sendMessage = async (messageDetails) => { const messagesCollection = client.db('chatDB').collection('messages'); await messagesCollection.insertOne(messageDetails); // Inform other users via WebSocket or similar };
// Middleware for validating request data const validateRequest = (req) => { // Validation logic here return isValid; }; export const sendMessage = async (messageDetails) => { if (!validateRequest(messageDetails)) { throw new Error("Invalid request data"); } // Remaining logic... };
Trong phần này, chúng ta sẽ xây dựng giao diện trò chuyện trực quan và phản hồi nhanh cho my-real-time-app
. Việc tích hợp các thành phần máy chủ của Next.js 13.4 sẽ cho phép cập nhật theo thời gian thực để mang lại trải nghiệm mượt mà cho người dùng.
// my-real-time-app/app/chat-interface.js import { useEffect, useState } from 'react'; import { getRecentMessages } from './actions/chat-action'; export default function ChatInterface() { const [messages, setMessages] = useState([]); useEffect(() => { async function loadMessages() { const recentMessages = await getRecentMessages(); setMessages(recentMessages); } loadMessages(); }, []); return ( <div className="chatBox"> {messages.map(msg => ( <p key={msg.id}>{msg.content}</p> ))} </div> ); }
// my-real-time-app/app/chat-interface.js const [socket, setSocket] = useState(null); useEffect(() => { const ws = new WebSocket("ws://your-backend-url/ws"); ws.onmessage = (event) => { const newMessage = JSON.parse(event.data); setMessages(prevMessages => [...prevMessages, newMessage]); }; setSocket(ws); return () => { ws.close(); }; }, []);
// my-real-time-app/app/chat-interface.js useEffect(() => { if (messages.length && "Notification" in window && Notification.permission === "granted") { const lastMessage = messages[messages.length - 1]; new Notification(`New message from ${lastMessage.sender}: ${lastMessage.content}`); } }, [messages]);
const HeavyComponent = React.lazy(() => import('./HeavyComponent')); function Chat() { return ( <React.Suspense fallback={<div>Loading...</div>}> <HeavyComponent /> </React.Suspense> ); }
React Server Components
của Next.js để phân tách logic:
Với các thành phần cốt lõi của ứng dụng thời gian thực của chúng tôi, điều cần thiết là đảm bảo rằng chúng hoạt động như mong đợi và có hiệu suất, khả năng mở rộng và mạnh mẽ. Phần này làm sáng tỏ các phương pháp thử nghiệm khác nhau phù hợp với các hệ thống thời gian thực như my-real-time-app
.
// cypress/integration/chat.spec.js describe('Chat functionality', () => { it('should send and receive messages in real-time', () => { cy.visit('/chat'); cy.get('[data-cy=messageInput]').type('Hello, World!'); cy.get('[data-cy=sendButton]').click(); cy.contains('Hello, World!').should('exist'); }); });
# artillery-config.yml config: target: '//my-real-time-app.com' phases: - duration: 300 arrivalRate: 20 scenarios: - flow: - emit: channel: 'chat' payload: message: 'Hello, World!'
$ artillery run artillery-config.yml
Node.js cung cấp các công cụ dựng sẵn để định hình và cờ --inspect
có thể được sử dụng với máy chủ phát triển Next.js để kích hoạt trình kiểm tra Node.js. Bằng cách sử dụng Công cụ dành cho nhà phát triển của Chrome, người ta có thể hiểu rõ hơn về các tắc nghẽn hiệu suất.
Đối với phía máy khách, các công cụ như tab Performance
trong Chrome DevTools có thể giúp xác định các tắc nghẽn kết xuất. Đặc biệt với các bản cập nhật theo thời gian thực, hãy đảm bảo rằng các kết xuất không cần thiết sẽ không xảy ra.
Ví dụ với SWR:
// my-real-time-app/app/chat-interface.js import useSWR from 'swr'; function ChatInterface() { const { data: messages } = useSWR('/api/messages', fetcher); // ... rest of the component }
Lưu ý: Thử nghiệm các ứng dụng thời gian thực yêu cầu kết hợp các kỹ thuật kiểm thử phần mềm tiêu chuẩn và một số kỹ thuật được thiết kế riêng cho các thách thức và đặc điểm của hệ thống thời gian thực. Đảm bảo chế độ thử nghiệm nghiêm ngặt cho my-real-time-app
, chúng tôi có thể đảm bảo trải nghiệm người dùng mượt mà và phản hồi nhanh, bất kể quy mô lưu lượng người dùng hoặc luồng dữ liệu.
Với kiến trúc nền tảng của ứng dụng thời gian thực của chúng tôi đã sẵn sàng, giờ đây, sự chú ý của chúng tôi chuyển sang tinh chỉnh các tính năng và hiệu suất của nó. Dưới đây là một số chiến lược để nâng cao trải nghiệm người dùng và tối ưu hóa my-real-time-app
:
// my-real-time-app/app/components/Message.js function Message({ content, status }) { return ( <div> <p>{content}</p> {status === 'read' && <span>✓ Read</span>} </div> ); }
// my-real-time-app/app/components/UserStatus.js function UserStatus({ isOnline }) { return ( <div> {isOnline ? <span className="online-indicator"></span> : <span className="offline-indicator"></span>} </div> ); }
// Example: Setting up compression with a WebSocket server const WebSocket = require('ws'); const wss = new WebSocket.Server({ perMessageDeflate: { zlibDeflateOptions: { // Add compression options here } } });
// Example: Simple retry logic with fetch let retries = 3; function fetchData(url) { fetch(url) .then(response => response.json()) .catch(error => { if (retries > 0) { retries--; fetchData(url); } else { console.error('Failed to fetch data after 3 retries'); } }); }
Lưu ý: Thành công liên tục của my-real-time-app
không chỉ phụ thuộc vào các chức năng cốt lõi của nó mà còn phụ thuộc vào các cải tiến tinh tế và tối ưu hóa liên tục để đảm bảo trải nghiệm người dùng mượt mà. Bằng cách kết hợp các chiến lược được liệt kê ở trên, chúng tôi sẵn sàng cung cấp cho người dùng trải nghiệm trò chuyện vượt trội, đáng tin cậy và thú vị.
Hành trình của chúng tôi với my-real-time-app
đã đưa chúng tôi từ thiết lập ban đầu với Next.js 13.4, thông qua xây dựng phụ trợ với các hành động của máy chủ, thiết kế trải nghiệm giao diện người dùng liền mạch và đảm bảo các khả năng thời gian thực đã được thử nghiệm và tối ưu hóa. Chúng tôi đã đào sâu vào các sắc thái của các thành phần máy chủ và máy khách, đảm bảo sự cân bằng hiệu quả giữa kết xuất phía máy chủ và tương tác phía máy khách.
Mặc dù my-real-time-app
đã đi được một chặng đường dài nhưng tiềm năng cho những cải tiến trong tương lai vẫn còn rất lớn:
Trước hết, xin chân thành cảm ơn bạn đã đồng hành cùng tôi qua mê cung phức tạp này của thế giới Next.js. Nếu bạn đã làm được điều này cho đến nay, xin chúc mừng! Nếu bạn đọc lướt qua một số phần, tôi không trách bạn – đã có lúc tôi muốn bỏ qua việc viết chúng!
Bạn đã bao giờ dành hàng giờ để gỡ lỗi một vấn đề, chỉ để nhận ra rằng bạn đã bỏ lỡ một dấu chấm phẩy chưa? Hoặc khi bạn vô tình xóa một phần thiết yếu trong mã của mình và ước cuộc sống có Ctrl + Z? Ôi, niềm vui của lập trình!
Vì vậy, lần tới khi mã của bạn từ chối hợp tác, hãy hít một hơi thật sâu, lấy một ít cà phê (hoặc trà, tôi không phán xét, bản thân tôi là một người hâm mộ matecocido ) và hãy nhớ rằng bạn không đơn độc trong việc này.