Sumérgete conmigo en este estudio de caso, donde nos embarcaremos en un viaje para construir una aplicación en tiempo real, aprovechando el poder y la delicadeza de las acciones del servidor Next.js. Ya sea que sea un desarrollador experimentado o simplemente se esté aventurando en el ámbito de las aplicaciones en tiempo real, hay una gran cantidad de información esperándolo.
Para ponerlo en perspectiva, considere algunos ejemplos omnipresentes:
Aplicaciones de mensajería instantánea : plataformas como WhatsApp y Telegram donde los mensajes se envían, reciben y ven sin demora.
Herramientas colaborativas : piense en Google Docs, donde varios usuarios pueden editar un documento simultáneamente, observando los cambios de los demás en tiempo real.
Live Stock Tickers : Plataformas que muestran precios de acciones que se actualizan instantáneamente con las fluctuaciones del mercado.
Juegos multijugador en línea : donde los jugadores interactúan entre sí y con el entorno sin latencia, lo que garantiza una experiencia de juego perfecta.
Entonces, ¿por qué se busca tanto la funcionalidad en tiempo real?
La creación de aplicaciones en tiempo real no está exenta de obstáculos:
Problemas de escalabilidad : las aplicaciones en tiempo real a menudo necesitan manejar numerosas conexiones simultáneas, lo que requiere una infraestructura robusta.
Integridad de los datos : garantizar que los datos en tiempo real permanezcan consistentes en varias interfaces de usuario puede ser un desafío, especialmente con múltiples ediciones o interacciones simultáneas.
Latencia : una aplicación en tiempo real es tan buena como su componente más lento. Garantizar retrasos mínimos requiere una optimización cuidadosa y un uso eficiente de los recursos.
Las acciones en el ecosistema React, aunque todavía experimentales, han provocado un cambio de paradigma al permitir a los desarrolladores ejecutar código asincrónico en respuesta a las interacciones del usuario.
Curiosamente, si bien no son exclusivos de Next.js o React Server Components, su uso a través de Next.js significa que está en el canal experimental de React.
Para aquellos que estén familiarizados con los formularios HTML, quizás recuerden pasar las URL al accesorio action
. Ahora, con Acciones, puede pasar directamente una función, haciendo que las interacciones sean más dinámicas e integradas.
<button action={() => { /* async function logic here */ }}>Click me!</button>
Las acciones de formulario representan una fusión ingeniosa de las acciones de React con la API <form>
estándar. Resuenan con el atributo formaction
primitiva en HTML, lo que hace posible que los desarrolladores mejoren los estados de carga progresiva y otras funcionalidades listas para usar.
<!-- Traditional HTML approach --> <form action="/submit-url"> <!-- form elements --> </form> <!-- With Next.js 13.4 Form Actions --> <form action={asyncFunctionForSubmission}> <!-- form elements --> </form>
Las funciones del servidor son esencialmente funciones que operan en el lado del servidor pero que pueden invocarse desde el cliente. Estos elevan las capacidades de representación del lado del servidor de Next.js a un nivel completamente nuevo.
Al pasar a Acciones del servidor , pueden entenderse como Funciones del servidor, pero se activan específicamente como una acción. Su integración con los elementos del formulario, especialmente a través de la propiedad action
, garantiza que el formulario permanezca interactivo incluso antes de que se cargue el JavaScript del lado del cliente. Esto se traduce en una experiencia de usuario más fluida, ya que la hidratación de React no es un requisito previo para el envío de formularios.
// A simple Server Action in Next.js 13.4 <form action={serverActionFunction}> <!-- form elements --> </form>
Por último, tenemos las mutaciones del servidor , que son un subconjunto de las acciones del servidor. Estos son particularmente poderosos cuando necesita modificar datos en el servidor y luego ejecutar respuestas específicas, como redirect
, revalidatePath
o revalidateTag
.
const serverMutationFunction = async () => { // Modify data logic here... // ... return { revalidatePath: '/updated-path' }; } <form action={serverMutationFunction}> <!-- form elements --> </form>
Notas: En resumen, el marco de acciones del servidor de Next.js 13.4, respaldado por acciones, acciones de formulario, funciones del servidor y mutaciones del servidor, incorpora un enfoque transformador para las aplicaciones web en tiempo real.
A medida que avancemos en nuestro estudio de caso, será testigo de primera mano de la destreza que estas características aportan. Entonces, ¡preparémonos para el emocionante viaje que tenemos por delante!
Primero, deberá habilitar las acciones del servidor en su proyecto Next.js. Simplemente agregue el siguiente código a su archivo next.config.js
:
module.exports = { experimental: { serverActions: true, }, }
Dentro de los componentes del servidor : una acción del servidor se puede definir fácilmente dentro de un componente del servidor, así:
export default function ServerComponent() { async function myAction() { 'use server' // ... } }
Con componentes de cliente : cuando utilice una acción de servidor dentro de un componente de cliente, cree la acción en un archivo separado y luego impórtelo.
// 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> ) }
Invocación personalizada : puede usar métodos personalizados como startTransition
para invocar acciones del servidor fuera de formularios, botones o entradas.
// 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 también ofrece mejoras progresivas, lo que permite que un <form>
funcione sin JavaScript. Las acciones del servidor se pueden pasar directamente a un <form>
, lo que hace que el formulario sea interactivo incluso si JavaScript está deshabilitado.
// app/components/example-client-component.js 'use client' import { handleSubmit } from './actions.js' export default function ExampleClientComponent({ myAction }) { return ( <form action={handleSubmit}> {/* ... */} </form> ) }
El cuerpo de solicitud máximo enviado a una acción de servidor es de 1 MB de forma predeterminada. Si es necesario, puede configurar este límite utilizando la opción serverActionsBodySizeLimit
:
module.exports = { experimental: { serverActions: true, serverActionsBodySizeLimit: '2mb', }, }
npx create-next-app@latest my-real-time-app
Reemplace my-real-time-app
con el nombre deseado para su proyecto. Este comando configura un nuevo proyecto Next.js con configuraciones predeterminadas.
Con la introducción de Next.js 13.4, App Router es una característica importante que permite a los desarrolladores utilizar diseños compartidos, enrutamiento anidado, manejo de errores y más. Está diseñado para funcionar junto con el directorio pages
existente, pero está alojado dentro de un nuevo directorio llamado app
.
Cree un directorio app
en la raíz de su proyecto.
De forma predeterminada, los componentes dentro del directorio app
son Componentes de servidor , que ofrecen un rendimiento óptimo y permiten que los desarrolladores los adopten fácilmente.
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
Componentes del servidor : ideal para partes no interactivas de su aplicación. Estos componentes se representan en el servidor y se envían al cliente como HTML. La ventaja aquí es un rendimiento mejorado, JavaScript del lado del cliente reducido y la capacidad de obtener datos o acceder a los recursos de back-end directamente.
Componentes de cliente : se utiliza para elementos de interfaz de usuario interactivos. Se procesan previamente en el servidor y luego se "hidratan" en el cliente para agregar interactividad.
Para diferenciar entre estos componentes, Next.js introdujo la directiva "use client"
. Esta directiva indica que un componente debe tratarse como un componente de cliente. Debe colocarse en la parte superior de un archivo de componente, antes de cualquier importación.
Por ejemplo, si tiene un contador interactivo, como en el código provisto, usará la directiva "use client"
para indicar que es un componente del lado del cliente.
Utilice los componentes del servidor de forma predeterminada (tal como están en el directorio app
).
Solo opte por los componentes de cliente cuando tenga casos de uso específicos, como agregar interactividad, utilizar API solo del navegador o aprovechar los ganchos de React que dependen del estado o las funcionalidades del navegador.
Notas: siguiendo esta estructura y configuración, estará bien encaminado para crear una aplicación en tiempo real de alto rendimiento con las acciones del servidor de Next.js 13.4.
El poder de Next.js 13.4 brilla al integrar funcionalidades de back-end en tiempo real en nuestro proyecto. Repasemos los pasos con ejemplos de código relevantes para nuestra my-real-time-app
.
Para nuestra my-real-time-app
, las acciones del servidor actúan como nuestro puente principal entre el frontend y el backend , lo que permite transacciones de datos eficientes sin la necesidad de API separadas.
// my-real-time-app/app/actions/index.js export * from './auth-action'; export * from './chat-action';
En my-real-time-app
, aprovechamos las acciones del servidor para agilizar el proceso de autenticación.
// 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... };
En esta sección, construiremos una interfaz de chat intuitiva y receptiva para my-real-time-app
. La integración de los componentes del servidor de Next.js 13.4 permitirá actualizaciones en tiempo real para una experiencia de usuario fluida.
// 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
de Next.js para dividir la lógica:
Con los componentes principales de nuestra aplicación en tiempo real en su lugar, es esencial garantizar que funcionen como se espera y que sean eficaces, escalables y sólidos. Esta sección arroja luz sobre varios enfoques de prueba diseñados para sistemas en tiempo real como nuestra 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 proporciona herramientas integradas para la generación de perfiles, y el indicador --inspect
se puede usar con el servidor de desarrollo Next.js para habilitar el inspector de Node.js. Mediante el uso de DevTools de Chrome, uno puede obtener información sobre los cuellos de botella de rendimiento.
Para el lado del cliente, herramientas como la pestaña Performance
en Chrome DevTools pueden ayudar a identificar cuellos de botella en la representación. Especialmente con las actualizaciones en tiempo real, asegúrese de que no se produzcan renderizaciones innecesarias.
Ejemplo con ROE:
// 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 }
Notas: Probar aplicaciones en tiempo real requiere una combinación de técnicas de prueba de software estándar y algunas diseñadas específicamente para los desafíos y características de los sistemas en tiempo real. Al garantizar un riguroso régimen de pruebas para my-real-time-app
, podemos garantizar una experiencia de usuario fluida y receptiva, independientemente de la escala del tráfico de usuarios o el flujo de datos.
Con la arquitectura fundamental de nuestra aplicación en tiempo real firmemente instalada, nuestra atención ahora se centra en perfeccionar sus características y rendimiento. Aquí hay algunas estrategias para mejorar la experiencia del usuario y optimizar nuestra 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'); } }); }
Notas: El éxito continuo de my-real-time-app
depende no solo de sus funcionalidades principales, sino también de las mejoras sutiles y las optimizaciones constantes que garantizan una experiencia de usuario sin fricciones. Al incorporar las estrategias enumeradas anteriormente, estamos preparados para ofrecer a nuestros usuarios una experiencia de chat superior que es confiable y agradable.
Nuestro viaje con my-real-time-app
nos llevó desde la configuración inicial con Next.js 13.4, a través de la creación de backend con acciones del servidor, el diseño de una experiencia de frontend perfecta y la garantía de que las capacidades en tiempo real se probaron y optimizaron. Profundizamos en los matices de los componentes del servidor y del cliente, asegurando un equilibrio efectivo entre la representación del lado del servidor y la interactividad del lado del cliente.
Si bien my-real-time-app
ha recorrido un largo camino, el potencial para futuras mejoras sigue siendo enorme:
En primer lugar, muchas gracias por viajar conmigo a través de este intrincado laberinto del mundo de Next.js. Si has llegado hasta aquí, ¡felicidades! Si hojeaste algunas partes, no te culpo, ¡hubo momentos en los que quería saltarme la escritura!
¿Alguna vez tuviste esos momentos en los que pasas horas depurando un problema, solo para darte cuenta de que te perdiste un punto y coma? ¿O cuando eliminas accidentalmente una parte esencial de tu código y deseas que la vida tenga Ctrl + Z? ¡Oh, las alegrías de la programación!
Entonces, la próxima vez que su código se niegue a cooperar, respire hondo, tome un café (o té, no juzgo, yo también soy un fanático del matecocido ) y recuerde que no está solo en esto.