Olá!
Se você for como eu, provavelmente já se surpreendeu com a interatividade perfeita dos aplicativos da Web em tempo real de hoje - aqueles chatbots que respondem instantaneamente, as notificações ao vivo que aparecem sem uma atualização de página e ferramentas colaborativas que atualizam em o piscar de olhos. A funcionalidade em tempo real tornou-se menos um luxo e mais uma expectativa na era digital.
Agora, se você está acompanhando os desenvolvimentos no mundo do Next.js, pode ter percebido os recursos interessantes da versão 13.4, especialmente o divisor de águas: ações do servidor. Você está curioso para saber como isso pode redefinir a maneira como criamos experiências em tempo real?
Bem, eu também!
Mergulhe comigo neste estudo de caso, onde embarcaremos em uma jornada para construir um aplicativo em tempo real, aproveitando o poder e a sutileza das ações do servidor Next.js. Quer você seja um desenvolvedor experiente ou apenas se aventurando no mundo dos aplicativos em tempo real, há um tesouro de insights esperando por você.
Vamos fazer a bola rolar, certo?
No cenário digital acelerado de hoje, o termo "tempo real" geralmente aparece em vários contextos, desde jogos e finanças até comunicação e mídia social. Mas o que exatamente significa "tempo real" no mundo dos aplicativos da web? eu
Vamos desmistificar isso.
Aplicações em tempo real são sistemas ou programas que respondem imediatamente às entradas do usuário ou eventos externos, oferecendo feedback instantâneo sem atrasos perceptíveis. Em termos mais simples, pense neles como plataformas vivas e dinâmicas que evoluem em "tempo real", refletindo o fluxo constante de informações no ecossistema digital moderno.
Para colocar isso em perspectiva, considere alguns exemplos onipresentes:
Aplicativos de mensagens instantâneas : plataformas como WhatsApp e Telegram onde as mensagens são enviadas, recebidas e vistas sem demora.
Ferramentas colaborativas : pense no Google Docs, onde vários usuários podem editar um documento simultaneamente, observando as alterações uns dos outros em tempo real.
Live Stock Tickers : plataformas que exibem preços de ações que se atualizam instantaneamente com as flutuações do mercado.
Jogos multijogador online : onde os jogadores interagem entre si e com o ambiente com latência zero, garantindo uma experiência de jogo perfeita.
Então, por que a funcionalidade em tempo real é tão procurada?
Construir aplicativos em tempo real não é isento de obstáculos:
Problemas de escalabilidade : os aplicativos em tempo real geralmente precisam lidar com várias conexões simultâneas, exigindo uma infraestrutura robusta.
Integridade dos dados : garantir que os dados em tempo real permaneçam consistentes em várias interfaces de usuário pode ser um desafio, especialmente com várias edições ou interações simultâneas.
Latência : um aplicativo em tempo real é tão bom quanto seu componente mais lento. Garantir atrasos mínimos requer otimização cuidadosa e uso eficiente de recursos.
Agora que preparamos o cenário com uma compreensão fundamental dos aplicativos em tempo real, vamos nos aprofundar em como o Next.js 13.4, com suas ações de servidor, surge como uma ferramenta essencial para desenvolvedores que desejam criar essas experiências imersivas.
No cenário em constante evolução do desenvolvimento da Web, o Next.js tem estado consistentemente na vanguarda, apresentando recursos que redefinem a forma como abordamos a criação de aplicativos. A versão 13.4 não é exceção, principalmente com sua ênfase nas ações do servidor. Mas antes de mergulharmos fundo, vamos esclarecer algumas terminologias:
As ações no ecossistema React, embora ainda experimentais, trouxeram uma mudança de paradigma ao permitir que os desenvolvedores executem código assíncrono em resposta às interações do usuário.
Curiosamente, embora não sejam exclusivos do Next.js ou React Server Components, seu uso por meio do Next.js significa que você está no canal experimental do React.
Para aqueles familiarizados com formulários HTML, você deve se lembrar de passar URLs para a propriedade action
. Agora, com o Actions, você pode passar diretamente uma função, tornando as interações mais dinâmicas e integradas.
<button action={() => { /* async function logic here */ }}>Click me!</button>
A integração do React com o Actions também oferece soluções integradas para atualizações otimistas. Isso enfatiza que, embora Actions sejam inovadores, os padrões ainda estão evoluindo e APIs mais recentes podem ser adicionadas para enriquecê-los ainda mais.
Ações de formulário representam um amálgama engenhoso de ações do React com a API <form>
padrão. Eles ressoam com o atributo formaction
primitiva em HTML, possibilitando que os desenvolvedores aprimorem os estados de carregamento progressivo e outras funcionalidades prontas para uso.
<!-- Traditional HTML approach --> <form action="/submit-url"> <!-- form elements --> </form> <!-- With Next.js 13.4 Form Actions --> <form action={asyncFunctionForSubmission}> <!-- form elements --> </form>
As funções do servidor são essencialmente funções que operam no lado do servidor, mas podem ser chamadas do cliente. Isso eleva os recursos de renderização do lado do servidor do Next.js a um nível totalmente novo.
Transicionando para Ações do Servidor , elas podem ser entendidas como Funções do Servidor, mas especificamente acionadas como uma ação. Sua integração com elementos de formulário, especialmente por meio da propriedade action
, garante que o formulário permaneça interativo mesmo antes do carregamento do JavaScript do lado do cliente. Isso se traduz em uma experiência de usuário mais suave, com a hidratação do React não sendo um pré-requisito para o envio do formulário.
// A simple Server Action in Next.js 13.4 <form action={serverActionFunction}> <!-- form elements --> </form>
Por último, temos Server Mutations , que são um subconjunto de Server Actions. Eles são particularmente poderosos quando você precisa modificar dados no servidor e, em seguida, executar respostas específicas, como redirect
, revalidatePath
ou revalidateTag
.
const serverMutationFunction = async () => { // Modify data logic here... // ... return { revalidatePath: '/updated-path' }; } <form action={serverMutationFunction}> <!-- form elements --> </form>
Observações: em resumo, a estrutura de ações do servidor do Next.js 13.4, sustentada por ações, ações de formulário, funções do servidor e mutações do servidor, incorpora uma abordagem transformadora para aplicativos da Web em tempo real.
À medida que avançamos em nosso estudo de caso, você testemunhará em primeira mão a proeza que esses recursos trazem para a mesa.
Então, vamos nos preparar para a emocionante jornada que temos pela frente!
No contexto da construção de um aplicativo em tempo real, as ações do servidor do Next.js 13.4 desempenham um papel crucial. Esses recursos alfa facilitam o gerenciamento de mutações de dados do lado do servidor, reduzem o JavaScript do lado do cliente e aprimoram progressivamente os formulários.
Primeiro, você precisará ativar as ações do servidor em seu projeto Next.js. Basta adicionar o seguinte código ao seu arquivo next.config.js
:
module.exports = { experimental: { serverActions: true, }, }
As Ações do Servidor podem ser definidas no Componente do Servidor que o utiliza ou em um arquivo separado para reutilização entre os Componentes do Cliente e do Servidor.
Veja como você pode criar e invocar ações do servidor:
Dentro dos Componentes do Servidor : Uma Ação do Servidor pode ser facilmente definida dentro de um Componente do Servidor, assim:
export default function ServerComponent() { async function myAction() { 'use server' // ... } }
Com componentes do cliente : ao usar uma ação do servidor dentro de um componente do cliente, crie a ação em um arquivo separado e importe-o.
// app/actions.js 'use server' export async function myAction() { // ... }
Importando e usando no componente cliente:
// app/client-component.js import { myAction } from './actions' export default function ClientComponent() { return ( <form action={myAction}> <button type="submit">Add to Cart</button> </form> ) }
Invocação personalizada : você pode usar métodos personalizados como startTransition
para invocar ações do servidor fora de formulários, botões ou 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> ) }
O Next.js 13.4 também oferece Progressive Enhancement, permitindo que um <form>
funcione sem JavaScript. As ações do servidor podem ser passadas diretamente para um <form>
, tornando o formulário interativo mesmo se o JavaScript estiver desabilitado.
// app/components/example-client-component.js 'use client' import { handleSubmit } from './actions.js' export default function ExampleClientComponent({ myAction }) { return ( <form action={handleSubmit}> {/* ... */} </form> ) }
O corpo máximo da solicitação enviada para uma ação do servidor é de 1 MB por padrão. Se necessário, você pode configurar esse limite usando a opção serverActionsBodySizeLimit
:
module.exports = { experimental: { serverActions: true, serverActionsBodySizeLimit: '2mb', }, }
Para começar a criar um aplicativo em tempo real usando o Next.js 13.4, a primeira etapa é criar um novo projeto. Você pode usar o comando padrão da CLI Next.js para inicializar seu projeto:
npx create-next-app@latest my-real-time-app
Substitua my-real-time-app
pelo nome desejado para o seu projeto. Este comando configura um novo projeto Next.js com configurações padrão.
Para funcionalidade em tempo real, existem certos pacotes e dependências que você pode precisar. Dependendo das especificidades do seu aplicativo, elas podem variar de bibliotecas WebSockets a assinaturas GraphQL e muito mais.
Verifique se você revisou os requisitos do projeto e adicionou as dependências necessárias.
No entanto, com o suporte do Next.js 13.4 para ações do servidor, já existe uma configuração integrada que oferece suporte ao processamento do lado do servidor, o que pode ajudar a obter alguns dos recursos em tempo real.
Com a introdução do Next.js 13.4, o App Router é um recurso significativo que permite aos desenvolvedores utilizar layouts compartilhados, roteamento aninhado, tratamento de erros e muito mais. Ele foi projetado para funcionar em conjunto com o diretório pages
existente, mas está alojado em um novo diretório chamado app
.
Para começar a usar o App Router:
Crie um diretório app
na raiz do seu projeto.
Adicione suas rotas ou componentes dentro deste diretório.
Por padrão, os componentes dentro do diretório app
são componentes do servidor , oferecendo desempenho ideal e permitindo que os desenvolvedores os adotem facilmente.
Aqui está um exemplo de estrutura:
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
Pensar em como os componentes são renderizados é crucial. Em SPAs (Single Page Applications) tradicionais, o React renderiza todo o aplicativo no lado do cliente. Com os componentes do servidor, grande parte do aplicativo é renderizado no servidor, levando a benefícios de desempenho. Aqui está uma diretriz:
Componentes do servidor : Ideal para partes não interativas do seu aplicativo. Esses componentes são renderizados no servidor e enviados ao cliente como HTML. A vantagem aqui é o desempenho aprimorado, JavaScript reduzido do lado do cliente e a capacidade de buscar dados ou acessar recursos de back-end diretamente.
Componentes do cliente : Usado para elementos interativos da interface do usuário. Eles são pré-renderizados no servidor e depois "hidratados" no cliente para adicionar interatividade.
Para diferenciar entre esses componentes, o Next.js introduziu a diretiva "use client"
. Esta diretiva indica que um componente deve ser tratado como um componente cliente. Ele deve ser colocado no topo de um arquivo de componente, antes de qualquer importação.
Por exemplo, se você tiver um contador interativo, como no código fornecido, usará a diretiva "use client"
para indicar que é um componente do lado do cliente.
Conforme você estrutura seu aplicativo, aqui estão algumas diretrizes:
Use componentes do servidor por padrão (como eles estão no diretório app
).
Opte por componentes do cliente apenas quando tiver casos de uso específicos, como adicionar interatividade, utilizar APIs somente do navegador ou aproveitar os ganchos do React que dependem do estado ou das funcionalidades do navegador.
Observações: seguindo essa estrutura e configuração, você estará no caminho certo para criar um aplicativo de tempo real de alto desempenho com as ações de servidor do Next.js 13.4.
O poder do Next.js 13.4 brilha ao integrar funcionalidades de back-end em tempo real em nosso projeto. Vamos percorrer as etapas com exemplos de código relevantes para nosso my-real-time-app
.
Para nosso my-real-time-app
, as ações do servidor atuam como nossa ponte principal entre o front-end e o back-end , permitindo transações de dados eficientes sem a necessidade de APIs separadas.
// my-real-time-app/app/actions/index.js export * from './auth-action'; export * from './chat-action';
Em my-real-time-app
, aproveitamos as ações do servidor para simplificar o processo de autenticação.
// 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 };
Para a funcionalidade de bate-papo:
// 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 };
Usando o MongoDB como nosso armazenamento de dados primário:
// 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.
Em nossas ações de chat:
// 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 };
Para segurança:
// 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... };
Nesta seção, construiremos uma interface de chat intuitiva e responsiva para my-real-time-app
. A integração dos componentes de servidor do Next.js 13.4 permitirá atualizações em tempo real para uma experiência de usuário tranquila.
Primeiramente, vamos criar a interface principal do chat:
// 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> ); }
Este componente busca mensagens recentes no carregamento e as renderiza em uma caixa de bate-papo.
Agora, configuraremos atualizações em tempo real usando um exemplo básico de WebSockets:
// 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(); }; }, []);
Esse gancho estabelece uma conexão WebSocket e atualiza a lista de mensagens quando uma nova mensagem é recebida.
Para um melhor UX, vamos notificar os usuários sobre novas mensagens:
// 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]);
Este efeito envia uma notificação ao navegador sempre que a lista de mensagens é atualizada com uma nova mensagem.
Para garantir uma experiência fluida:
const HeavyComponent = React.lazy(() => import('./HeavyComponent')); function Chat() { return ( <React.Suspense fallback={<div>Loading...</div>}> <HeavyComponent /> </React.Suspense> ); }
React Server Components
do Next.js para dividir a lógica:
Lembre-se da documentação anterior, os componentes do servidor podem ser usados para partes não interativas, enquanto os componentes do cliente podem lidar com as partes interativas, reduzindo a quantidade de JavaScript enviada ao cliente.
Por exemplo, em nosso chat, o histórico de mensagens pode ser um componente do servidor, enquanto o campo de entrada e o botão de envio, que requerem interatividade do lado do cliente, podem ser componentes do cliente.
Com os principais componentes de nosso aplicativo em tempo real implantados, é essencial garantir que eles funcionem conforme o esperado e sejam eficientes, escaláveis e robustos. Esta seção esclarece várias abordagens de teste personalizadas para sistemas em tempo real, como nosso my-real-time-app
.
Para aplicativos em tempo real, os testes de ponta a ponta são cruciais. Vamos configurar um exemplo com o Cypress:
// 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'); }); });
Isso ajudará a entender como o sistema se comporta sob números significativos de usuários ou mensagens:
# artillery-config.yml config: target: 'http://my-real-time-app.com' phases: - duration: 300 arrivalRate: 20 scenarios: - flow: - emit: channel: 'chat' payload: message: 'Hello, World!'
$ artillery run artillery-config.yml
O Node.js fornece ferramentas integradas para criação de perfil, e o sinalizador --inspect
pode ser usado com o servidor de desenvolvimento Next.js para habilitar o inspetor Node.js. Ao usar o DevTools do Chrome, é possível obter insights sobre gargalos de desempenho.
Para o lado do cliente, ferramentas como a guia Performance
no Chrome DevTools podem ajudar a identificar gargalos de renderização. Especialmente com atualizações em tempo real, certifique-se de que renderizações desnecessárias não estejam acontecendo.
Aplicações em tempo real geralmente envolvem manter o estado do cliente em sincronia com o servidor. Bibliotecas como SWR ou React Query ajudam a tornar isso mais fácil, oferecendo recursos como busca automática, armazenamento em cache e sincronização em tempo real.
Exemplo com 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 }
Para escalabilidade de back-end, especialmente com WebSockets, considere o uso de uma solução como Redis para gerenciar o estado em várias instâncias de seu servidor. Dessa forma, se uma instância do servidor receber uma mensagem, ela poderá transmiti-la para clientes conectados a outras instâncias do servidor.
Certifique-se de que suas consultas de banco de dados, especialmente aquelas que são executadas com frequência em aplicativos em tempo real, sejam otimizadas. Indexe colunas essenciais e considere o uso de soluções de cache de banco de dados para dados acessados com frequência.
Notas: O teste de aplicativos em tempo real requer uma combinação de técnicas de teste de software padrão e algumas adaptadas especificamente para os desafios e características dos sistemas de tempo real. Garantindo um regime de teste rigoroso para my-real-time-app
, podemos garantir uma experiência de usuário suave e responsiva, independentemente da escala do tráfego do usuário ou do fluxo de dados.
Com a arquitetura fundamental de nosso aplicativo de tempo real firmemente estabelecida, nossa atenção agora se volta para refinar seus recursos e desempenho. Aqui estão algumas estratégias para melhorar a experiência do usuário e otimizar nosso my-real-time-app
:
Forneça feedback visual aos usuários quando suas mensagens forem lidas pelo destinatário. Isso aprimora a natureza interativa dos bate-papos em tempo real.
// my-real-time-app/app/components/Message.js function Message({ content, status }) { return ( <div> <p>{content}</p> {status === 'read' && <span>✓ Read</span>} </div> ); }
Mostre um indicador ao lado do nome ou avatar de um usuário quando ele estiver online.
// 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> ); }
Atualizações em lote do lado do servidor, quando possível, para reduzir o número de mensagens enviadas ao cliente.
Para aplicativos com atualizações de alta frequência, considere a compactação de mensagens WebSocket para reduzir os dados transferidos e aumentar a velocidade.
// Example: Setting up compression with a WebSocket server const WebSocket = require('ws'); const wss = new WebSocket.Server({ perMessageDeflate: { zlibDeflateOptions: { // Add compression options here } } });
Se você perceber atualizações rápidas e consecutivas de clientes, considere recuperá-las para consolidá-las em menos atualizações mais significativas.
Para seções críticas de seu aplicativo em que a integridade dos dados é fundamental, considere a adoção de um padrão de fornecimento de eventos. Isso garante que cada alteração no estado do aplicativo seja capturada como um evento, permitindo recuperação confiável e reprodução de eventos.
Certifique-se de que, se uma mensagem não for enviada ou uma atualização não for concluída devido a problemas de rede, haja um mecanismo de repetição em vigor.
// 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'); } }); }
Faça backup de dados regularmente e certifique-se de ter um plano e processos claros para recuperar dados em caso de falhas. Use replicação de banco de dados ou bancos de dados distribuídos como Cassandra para tolerância a falhas.
Observações: O sucesso contínuo do my-real-time-app
depende não apenas de suas principais funcionalidades, mas também de aprimoramentos sutis e otimizações constantes que garantem uma experiência de usuário sem atrito. Ao incorporar as estratégias listadas acima, estamos preparados para oferecer aos nossos usuários uma experiência de bate-papo superior, confiável e agradável.
Nossa jornada com my-real-time-app
nos levou desde a configuração inicial com o Next.js 13.4, passando pela construção de back-end com ações de servidor, projetando uma experiência de front-end perfeita e garantindo que os recursos em tempo real fossem testados e otimizados. Nós nos aprofundamos nas nuances dos componentes do servidor e do cliente, garantindo um equilíbrio efetivo entre a renderização do lado do servidor e a interatividade do lado do cliente.
A introdução de ações de servidor no Next.js 13.4 revolucionou nossa abordagem para aplicativos em tempo real. Isso nos permitiu criar um aplicativo de bate-papo altamente interativo que aproveita os pontos fortes da renderização do servidor e do cliente. Isso não apenas otimizou o desempenho, mas também facilitou interações contínuas do usuário sem comprometer a segurança ou a eficiência.
Embora my-real-time-app
tenha percorrido um longo caminho, o potencial para aprimoramentos futuros permanece vasto:
À medida que você embarca em sua jornada com aplicativos em tempo real e se aprofunda nas funcionalidades e complexidades do Next.js, aqui está uma lista selecionada de recursos que podem orientar, inspirar e educá-lo ainda mais:
Em primeiro lugar, um enorme obrigado por viajar comigo através deste intrincado labirinto do mundo Next.js. Se você chegou até aqui, parabéns! Se você folheou algumas partes, eu não o culpo – houve momentos em que eu quis pular a escrita!
Construir aplicativos em tempo real é, em muitos aspectos, uma montanha-russa de emoções. Alguns dias me sinto um gênio da codificação, enquanto em outros questiono todas as escolhas de vida que me levaram a me tornar um desenvolvedor.
Já teve aqueles momentos em que você passa horas depurando um problema, apenas para perceber que perdeu um ponto e vírgula? Ou quando você exclui acidentalmente uma parte essencial do seu código e deseja que a vida tenha um Ctrl + Z? Oh, as alegrias da programação!
Mas eis o seguinte: em meio a todos os facepalms e puxões de cabelo ocasionais, há uma magia indescritível em ver sua criação ganhar vida, em tempo real. É aquela pequena centelha de alegria quando seu código é executado sem erros, a satisfação quando os usuários adoram seu aplicativo e o orgulho de saber que você criou algo do zero.
Para todos os desenvolvedores iniciantes que estão lendo isto: contratempos, frustrações e 'por que isso não está funcionando!?' momentos são parte integrante da nossa jornada. Eles não são sinais de que você está falhando, mas sim trampolins para se tornar melhor.
Portanto, da próxima vez que seu código se recusar a cooperar, respire fundo, pegue um café (ou chá, não julgo, eu também sou fã de matecocido ) e lembre-se de que você não está sozinho nisso.
Continue ultrapassando os limites, continue aprendendo e lembre-se de que cada linha de código, quer funcione ou não, adiciona um capítulo à sua história de desenvolvedor.
E se você precisar de uma risada ou de um ombro para chorar (virtualmente, é claro), saiba que eu já estive lá, fiz isso e fiquei frustrado o suficiente para pensar em jogar meu laptop pela janela!
Um brinde a mais aventuras de codificação e menos bugs induzidos por ponto-e-vírgula!
Felicidades e codificação feliz!