Next.js 서버 액션의 강력함과 정교함을 활용하여 실시간 애플리케이션을 구축하는 여정을 시작하게 될 이 사례 연구에 저와 함께 참여해 보세요. 숙련된 개발자이든 실시간 앱 영역을 탐험하는 사람이든 관계없이 수많은 통찰력이 여러분을 기다리고 있습니다.
이를 관점에서 살펴보려면 다음과 같은 몇 가지 유비쿼터스 예를 고려하십시오.
인스턴트 메시징 앱 : 메시지를 지연 없이 보내고 받고 볼 수 있는 WhatsApp , Telegram과 같은 플랫폼입니다.
공동 작업 도구 : 여러 사용자가 동시에 문서를 편집하고 서로의 변경 사항을 실시간으로 관찰할 수 있는 Google Docs를 생각해 보세요.
실시간 주식 시세 : 시장 변동에 따라 즉시 업데이트되는 주가를 표시하는 플랫폼입니다.
온라인 멀티플레이어 게임 : 플레이어가 대기 시간 없이 서로 및 환경과 상호 작용하여 원활한 게임 경험을 보장합니다.
그렇다면 실시간 기능이 그토록 요구되는 이유는 무엇입니까?
실시간 애플리케이션을 구축하는 데 장애물이 없는 것은 아닙니다.
확장성 문제 : 실시간 앱은 종종 수많은 동시 연결을 처리해야 하므로 강력한 인프라가 필요합니다.
데이터 무결성 : 다양한 사용자 인터페이스에서 실시간 데이터의 일관성을 유지하는 것은 어려울 수 있으며, 특히 여러 동시 편집 또는 상호 작용의 경우 더욱 그렇습니다.
지연 시간 : 실시간 앱의 성능은 가장 느린 구성 요소만큼 좋습니다. 지연을 최소화하려면 신중한 최적화와 리소스의 효율적인 사용이 필요합니다.
React 생태계의 작업은 아직 실험적이지만 개발자가 사용자 상호 작용에 응답하여 비동기 코드를 실행할 수 있도록 함으로써 패러다임 전환을 가져왔습니다.
흥미롭게도 Next.js 또는 React Server Components에만 국한되지는 않지만 Next.js를 통해 사용하면 React 실험 채널에 있다는 의미입니다.
HTML 양식에 익숙한 사람들이라면 URL을 action
prop에 전달하는 것을 기억할 것입니다. 이제 Actions를 사용하면 함수를 직접 전달하여 상호 작용을 더욱 동적이고 통합적으로 만들 수 있습니다.
<button action={() => { /* async function logic here */ }}>Click me!</button>
Form Actions는 React의 Actions와 표준 <form>
API의 독창적인 결합을 나타냅니다. 이는 HTML의 기본 formaction
속성과 공명하여 개발자가 프로그레시브 로딩 상태 및 기타 기능을 기본적으로 향상시킬 수 있도록 해줍니다.
<!-- Traditional HTML approach --> <form action="/submit-url"> <!-- form elements --> </form> <!-- With Next.js 13.4 Form Actions --> <form action={asyncFunctionForSubmission}> <!-- form elements --> </form>
서버 기능은 기본적으로 서버 측에서 작동하지만 클라이언트에서 호출할 수 있는 기능입니다. 이는 Next.js의 서버 측 렌더링 기능을 완전히 새로운 수준으로 향상시킵니다.
서버 작업 으로 전환하면 서버 기능으로 이해될 수 있지만 구체적으로 작업으로 트리거되는 기능입니다. 특히 action
prop을 통한 양식 요소와의 통합은 클라이언트 측 JavaScript가 로드되기 전에도 양식이 대화형으로 유지되도록 보장합니다. 이는 양식 제출을 위한 필수 조건이 아닌 React 수화를 통해 보다 원활한 사용자 경험으로 해석됩니다.
// A simple Server Action in Next.js 13.4 <form action={serverActionFunction}> <!-- form elements --> </form>
마지막으로 서버 작업의 하위 집합인 서버 돌연변이가 있습니다. 이는 서버의 데이터를 수정한 다음 redirect
, revalidatePath
또는 revalidateTag
와 같은 특정 응답을 실행해야 할 때 특히 강력합니다.
const serverMutationFunction = async () => { // Modify data logic here... // ... return { revalidatePath: '/updated-path' }; } <form action={serverMutationFunction}> <!-- form elements --> </form>
참고: 요약하자면, Actions, Form Actions, Server Functions 및 Server Mutations를 기반으로 하는 Next.js 13.4의 Server Actions 프레임워크는 실시간 웹 애플리케이션에 대한 혁신적인 접근 방식을 구현합니다.
사례 연구를 진행하면서 이러한 기능이 제공하는 우수성을 직접 목격하게 될 것입니다. 그럼 앞으로의 흥미진진한 여정을 준비하세요!
먼저 Next.js 프로젝트에서 서버 작업을 활성화해야 합니다. next.config.js
파일에 다음 코드를 추가하기만 하면 됩니다.
module.exports = { experimental: { serverActions: true, }, }
서버 구성 요소 내 : 서버 작업은 다음과 같이 서버 구성 요소 내에서 쉽게 정의할 수 있습니다.
export default function ServerComponent() { async function myAction() { 'use server' // ... } }
클라이언트 구성 요소 사용 : 클라이언트 구성 요소 내에서 서버 작업을 사용하는 경우 별도의 파일에 작업을 만든 다음 가져옵니다.
// 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> ) }
사용자 정의 호출 : startTransition
과 같은 사용자 정의 메소드를 사용하여 양식, 버튼 또는 입력 외부에서 서버 작업을 호출할 수 있습니다.
// 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는 또한 <form>
이 JavaScript 없이 작동할 수 있도록 하는 점진적인 향상 기능을 제공합니다. 서버 작업을 <form>
에 직접 전달할 수 있으므로 JavaScript가 비활성화된 경우에도 양식을 대화형으로 만들 수 있습니다.
// app/components/example-client-component.js 'use client' import { handleSubmit } from './actions.js' export default function ExampleClientComponent({ myAction }) { return ( <form action={handleSubmit}> {/* ... */} </form> ) }
서버 작업으로 전송되는 최대 요청 본문은 기본적으로 1MB입니다. 필요한 경우 serverActionsBodySizeLimit
옵션을 사용하여 이 제한을 구성할 수 있습니다.
module.exports = { experimental: { serverActions: true, serverActionsBodySizeLimit: '2mb', }, }
npx create-next-app@latest my-real-time-app
my-real-time-app
원하는 프로젝트 이름으로 바꾸세요. 이 명령은 기본 구성으로 새 Next.js 프로젝트를 설정합니다.
Next.js 13.4의 도입으로 앱 라우터는 개발자가 공유 레이아웃, 중첩 라우팅, 오류 처리 등을 활용할 수 있게 해주는 중요한 기능입니다. 이는 기존 pages
디렉토리와 함께 작동하도록 설계되었지만 app
이라는 새 디렉토리 내에 보관됩니다.
프로젝트 루트에 app
디렉터리를 만듭니다.
기본적으로 app
디렉터리 내의 구성 요소는 서버 구성 요소 로, 최적의 성능을 제공하고 개발자가 쉽게 채택할 수 있습니다.
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
서버 구성 요소 : 애플리케이션의 비대화형 부분에 이상적입니다. 이러한 구성 요소는 서버에서 렌더링되어 클라이언트에 HTML로 전송됩니다. 여기서의 장점은 성능 향상, 클라이언트 측 JavaScript 감소, 데이터 가져오기 또는 백엔드 리소스 직접 액세스 기능입니다.
클라이언트 구성 요소 : 대화형 UI 요소에 사용됩니다. 서버에서 미리 렌더링된 다음 클라이언트에서 "수화"되어 상호작용성을 추가합니다.
이러한 구성 요소를 구별하기 위해 Next.js는 "use client"
지시문을 도입했습니다. 이 지시문은 구성 요소가 클라이언트 구성 요소로 처리되어야 함을 나타냅니다. 가져오기 전에 구성요소 파일의 맨 위에 배치해야 합니다.
예를 들어, 제공된 코드와 같이 대화형 카운터가 있는 경우 "use client"
지시어를 사용하여 클라이언트 측 구성 요소임을 나타냅니다.
기본적으로 서버 구성 요소를 사용합니다( app
디렉터리에 있으므로).
대화형 기능 추가, 브라우저 전용 API 활용, 상태 또는 브라우저 기능에 따라 달라지는 React 후크 활용과 같은 특정 사용 사례가 있는 경우에만 클라이언트 구성 요소를 선택하세요.
참고: 이 구조와 설정에 따라 Next.js 13.4의 서버 작업을 사용하여 고성능 실시간 애플리케이션을 구축할 수 있습니다.
Next.js 13.4의 힘은 실시간 백엔드 기능을 우리 프로젝트에 통합할 때 빛을 발합니다. my-real-time-app
에 대한 관련 코드 예제를 통해 단계를 살펴보겠습니다.
my-real-time-app
의 경우 서버 작업은 프런트엔드 와 백엔드 사이의 기본 브리지 역할을 하여 별도의 API 없이도 효율적인 데이터 트랜잭션이 가능합니다.
// my-real-time-app/app/actions/index.js export * from './auth-action'; export * from './chat-action';
my-real-time-app
에서는 서버 작업을 활용하여 인증 프로세스를 간소화합니다.
// 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... };
이 섹션에서는 my-real-time-app
위한 직관적이고 반응이 빠른 채팅 인터페이스를 구성하겠습니다. Next.js 13.4의 서버 구성요소 통합으로 원활한 사용자 경험을 위한 실시간 업데이트가 가능해집니다.
// 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
사용하여 로직을 분할하세요.
실시간 애플리케이션의 핵심 구성 요소가 제대로 작동하려면 해당 구성 요소가 예상대로 작동하고 성능, 확장성 및 견고성을 보장하는 것이 중요합니다. 이 섹션에서는 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는 프로파일링을 위한 내장 도구를 제공하며 --inspect
플래그를 Next.js 개발 서버와 함께 사용하여 Node.js 검사기를 활성화할 수 있습니다. Chrome의 DevTools를 사용하면 성능 병목 현상에 대한 통찰력을 얻을 수 있습니다.
클라이언트 측의 경우 Chrome DevTools의 Performance
탭과 같은 도구를 사용하면 렌더링 병목 현상을 식별하는 데 도움이 될 수 있습니다. 특히 실시간 업데이트의 경우 불필요한 렌더링이 발생하지 않는지 확인하세요.
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 }
참고: 실시간 애플리케이션을 테스트하려면 표준 소프트웨어 테스트 기술과 실시간 시스템의 문제 및 특성에 맞게 특별히 맞춤화된 기술이 필요합니다. my-real-time-app
에 대한 엄격한 테스트 체제를 보장함으로써 사용자 트래픽이나 데이터 흐름의 규모에 관계없이 원활하고 반응이 빠른 사용자 경험을 보장할 수 있습니다.
실시간 애플리케이션의 기본 아키텍처가 확고하게 자리잡았으므로 이제 우리의 관심은 기능과 성능을 개선하는 데로 옮겨졌습니다. 사용자 경험을 향상하고 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'); } }); }
참고: my-real-time-app
의 지속적인 성공은 핵심 기능뿐만 아니라 마찰 없는 사용자 경험을 보장하는 미묘한 개선과 지속적인 최적화에 달려 있습니다. 위에 나열된 전략을 통합함으로써 우리는 사용자에게 안정적이고 즐거운 우수한 채팅 경험을 제공할 준비가 되어 있습니다.
my-real-time-app
사용한 우리의 여정은 Next.js 13.4를 사용한 초기 설정부터 서버 작업을 사용한 백엔드 구축, 원활한 프런트엔드 경험 설계, 실시간 기능 테스트 및 최적화를 거쳐 이루어졌습니다. 우리는 서버 측 렌더링과 클라이언트 측 상호 작용 간의 효과적인 균형을 보장하면서 서버와 클라이언트 구성 요소의 미묘한 차이를 깊이 조사했습니다.
my-real-time-app
많은 발전을 이루었지만 향후 개선 가능성은 여전히 엄청납니다.
먼저, 이 복잡한 Next.js 세계의 미로를 저와 함께 여행해 주셔서 진심으로 감사드립니다 . 여기까지 완료하셨다면 축하드립니다! 몇몇 부분을 훑어보셨다고 해서 비난하는 것은 아닙니다. 글을 건너뛰고 싶을 때도 있었습니다!
문제를 디버깅하는 데 몇 시간을 소비했지만 세미콜론을 놓쳤다는 것을 깨달은 순간이 있었습니까? 아니면 실수로 코드의 중요한 부분을 삭제하고 인생에 Ctrl + Z가 있었으면 좋겠나요? 아, 프로그래밍의 즐거움이군요!
따라서 다음 번에 코드가 협조를 거부하면 심호흡을 하고 커피(또는 차, 판단하지 마세요. 저는 마테코시도 팬입니다)를 마시고 이 일에 혼자가 아니라는 것을 기억하십시오.