Construindo um Painel de Blog Dinâmico com Next.js
Vitor Alecrim
Posted on December 1, 2023
Apresentação
Olá, como vai? Aqui é o Vítor, retornando com um novo projeto para ajudá-lo a aprimorar suas habilidades de programador. Já faz um tempo desde que publiquei um tutorial. Nos últimos meses, tirei um tempo para descansar e me dedicar a outras atividades. Durante esse período, desenvolvi um pequeno projeto web: um blog, que se tornou o foco deste tutorial.
Neste guia, vamos criar o frontend de uma página de blog capaz de renderizar a escrita markdown. A aplicação incluirá rotas públicas e privadas, autenticação de usuário, e a capacidade de escrever texto em Markdown, com a adição de fotos, exibição de artigos e muito mais.
Sinta-se à vontade para personalizar a sua aplicação da maneira que preferir, até mesmo eu incentivo isso.
Você pode acessar o repositório dessa aplicação aqui:
Vamos utilizar a versão mais recente do framework Next.js, que, no momento da redação deste tutorial, é a versão 13.4.
Execute o seguinte comando para criar o projeto:
npx create-next-app myblog
Durante a instalação, selecione as configurações do template. Neste tutorial,usarei TypeScript como linguagem e o framework de CSS, Tailwind CSS, para o estilo da nossa aplicação.
Configuração
Agora vamos instalar todas as bibliotecas que faremos uso.
Markdown
npm i markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
Na raiz do projeto, no arquivo next.config.js, vamos configurar o endereço do domínio de onde iremos acessar as imagens dos nossos artigos. Para este tutorial, ou se estiver usando um servidor local, utilizaremos localhost.
Certifique-se de incluir essa configuração para garantir o correto carregamento das imagens em sua aplicação.
Para saber mais sobre middlewares e tudo o que você pode fazer com ele, acesse a documentação
Configurando Rota de Autenticação.
Dentro da pasta /app, crie um arquivo chamado route.ts em api/auth/[...nextauth]. Ele conterá a configuração de nossas rotas, conectando-se à nossa API de autenticação usando o CredentialsProvider.
O CredentialsProvider permite que você lide com o login usando credenciais arbitrárias, como nome de usuário e senha, domínio ou autenticação de dois fatores ou dispositivo de hardware etc.
Primeiramente, na raiz do seu projeto, crie um arquivo .env.local e adicione um token que será usado como o nosso secret.
.env.local
NEXTAUTH_SECRET = SubsTituaPorToken
Em seguida, vamos escrever nosso sistema de autenticação, onde esse NEXTAUTH_SECRET será adicionado ao nosso secret no arquivo src/app/auth/[...nextauth]/routes.ts.
Vamos criar um provedor de autenticação, um context, que irá compartilhar os dados do nosso usuário pelas páginas da nossa rota privada. Vamos utilizá-lo posteriormente para encapsular um de nossos layout.tsx.
Crie um arquivo em src/context/auth-provider.tsx com o seguinte conteúdo:
No geral, em nossa aplicação, usaremos o Tailwind CSS para criar nosso estilo. No entanto, em alguns lugares, iremos compartilhar classes de CSS personalizadas entre páginas e componentes.
/*global.css*/.container{max-width:1100px;width:100%;margin:0pxauto;}.image-container{position:relative;width:100%;height:5em;padding-top:56.25%;/* Aspect ratio 16:9 (dividindo a altura pela largura) */}.image-containerimg{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;}@keyframesspinner{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}.loading-spinner{width:50px;height:50px;border:10pxsolid#f3f3f3;border-top:10pxsolid#293d71;border-radius:50%;animation:spinner1.5slinearinfinite;}
Layouts
agora vamos escrever os layouts, privados e públcios.
app/layout.tsx
importtype{Metadata}from"next";import{Inter}from"next/font/google";import"./globals.css";importProviderfrom"@/context/auth-provider";import{getServerSession}from"next-auth";import{authOptions}from"./api/auth/[...nextauth]/route";constinter=Inter({subsets:["latin"]});exportconstmetadata:Metadata={title:"Markdown Text Editor",description:"Created by <@vitorAlecrim>",};exportdefaultasyncfunctionRootLayout({children,}:{children:React.ReactNode;}){constsession=awaitgetServerSession(authOptions);return (<Providersession={session}><htmllang="en"><bodyclassName={inter.className}>{children}</body></html></Provider>);}
Nossa aplicação fará várias chamadas à nossa API, e você pode adaptar essa aplicação para usar qualquer API externa. No nosso exemplo, estamos utilizando a nossa aplicação local. Caso não tenha visto o tutorial do backend e a criação do servidor, acesse.
Em src/services/, vamos escrever as funções abaixo:
authService.ts: função responsável por autenticar nosso usuário no servidor.
getArticles.tsx: função responsável por chamar todos os artigos salvos em nosso banco de dados:
exportdefaultasyncfunctiongetArticals(){try{constres=awaitfetch("http://localhost:8080/articles/getAll",{cache:"no-cache",});constdata=awaitres.json();returndata;}catch (error){console.log("something wrong just happend",error);}}
postArtcile.tsx: função responsável por registrar os dados do artigo em nosso servidor.
import{IProp}from"@/interfaces/services.interface";exportdefaultasyncfunctionpostArtical(prop:IProp){const{token,title,doc,imageUrl}=prop;constformData=newFormData();formData.append('title',title);formData.append('thumb',imageUrl);formData.append('content',doc);constheaders={'x-access-token':token};try{constres=awaitfetch('http://localhost:8080/articles/add',{method:'POST',headers:headers,body:formData})constresult=awaitres.json();returnresult;}catch(error){console.log('Error:',error);console.log('something wrong just happend',awaiterror);}}
editArticle.tsx: função responsável por modificar um artigo específico dentro do banco de dados.
import{IProp}from"@/interfaces/services.interface";exportdefaultasyncfunctioneditArtical(prop:IProp){const{id,token,imageUrl,title,doc}=prop;constformData=newFormData();formData.append("title",title);formData.append("thumb",imageUrl);formData.append("content",doc);constheaders={"x-access-token":token,};try{constres=awaitfetch(`http://localhost:8080/articles/edit/${id}`,{method:"PATCH",headers:headers,body:formData,});constresult=awaitres.json();returnresult;}catch (error){console.log("Error:",error);console.log("something wrong just happend",awaiterror);throwerror;}}
deleteArticle.tsx: função responsável por remover um artigo específico de nosso banco de dados:
Um componente de paginação usado em nossa página de exibição de todos os nossos artigos, em nossa rota privada. Você pode encontrar um artigo mais detalhado sobre a escrita deste componenteaqui
Cartão de exibição dos artigos escritos.
Este componente também contém um link que levará tanto à página de exibição do artigo quanto à página de edição de um artigo previamente escrito.
Componente responsável por exibir o texto que estamos escrevendo em nosso editor Ele faz uso de uma biblioteca diferente da article. Caso queira, você pode adaptar o componente para usar a mesma biblioteca.
Componente responsável por executar chamadas de api e a exibição do retorno de sua resposta.
Aqui faremos uso de duas chamadas de api através das funções que escrevemos:
getArticles.ts - nos retorna todos os artigos que serão exibidos no componente.
removeArticle - remove um artigo específico de nossa lista e do nosso servidor.
Faremos uso do componente Pagination.tsx, escrito previamente para dividir o número de nossos artigos em páginas.
A seguir, passaremos por cada uma de nossas páginas, divididas por suas respectivas rotas.
Públicas
Login
Esta é a página inicial de nossa aplicação. Trata-se de uma página simples; você pode modificá-la conforme entender. Nela, faremos uso da função signin provida pela biblioteca de navegação next-auth.
Para criar a página de leitura de artigos, vamos desenvolver uma página dinâmica.
Toda plataforma de blog que você já acessou provavelmente possui uma página dedicada à leitura de artigos, acessível via URL. A razão para isso é uma rota de página dinâmica. Felizmente, o Next.js facilita isso com seu novo método AppRouter, tornando nossa vida muito mais fácil.
Primeiro: precisamos criar a rota em nossa estrutura, adicionando uma pasta [id]. Isso resultará na seguinte estrutura, pages/(public)/articles/[id]/pages.tsx.
O id corresponde ao slug da nossa rota de navegação.
params é uma propriedade passada através da arvore de nossa aplicação contendo o slug de navegação.
E por fim,
uma vez a página pronta, ao acessar, por exemplo, localhost:3000/articles/1 no navegador, você terá acesso ao artigo com o ID fornecido.
No nosso caso, o id será passado através da navegação quando clicarmos em um dos componentes ArticleCards.tsx, que serão renderizados na página principal da nossa rota privada.
Aqui estão nossas páginas privadas que poderão apenas ser acessadas uma vez que o usuário está autenticado em nossa aplicação.
Home
Dentro da nossa pasta app/pages/ quando algum arquivo é declarado dentro de (), significa que aquela rota é /.
No nosso caso, a pasta (Home), refere-se a página inicial de nossa rota privada. Ela é a primeira página que o usuário ver ao se autenticar no sistema. Essa página irá exibir a lista de artigos de nosso banco de dados.
Os dados serão processados pelo nosso componente ArticlesList.tsx. Se você ainda não escreveu esse código, volte à seção de componentes.
Essa é uma das páginas mais importantes de nossa aplicação, através delavamos poder registrar os nossos artigos.
Essa página permitirá o usuário.
Escrever um artigo em formato markdown.
Atribuir uma imagem ao artigo.
Acesso a prévia do texto em markdown antes de envia-lo ao servidor.
A página faz uso de alguns hooks:
useCallBack - utilizado para memorizar funções.
useState - permite você adicionar uma state variavel ao nosso componente.
useSession - nos permite saber se o usuário está autenticado, e nos permite obter o token de autenticação.
Para isso iremos usar dois componentes:
TextEditor.tsx: editor de texto que escrevemos previamente.
Preview.tsx: componente de exibição de arquivo em formato markdown.
Durante a construção desta página faremos uso da nossa API.
POST: utilizando a nossa função,postArtical, vamos enviar o artigo ao servidor.
Também faremos uso do hook useSession, provido pela biblioteca next-auth, para obtermos o token de autenticação de usuário que será utilizado para realizarmos o registro do artigo no servidor.
três chamadas distintas de API.
Em app/pages/(private)/newArticle/page.tsx
Página similar a de Novo Artigo(newArticle), com algumas diferenças.
Primeiro nós definimos uma ronta dinâmica, onde recebemos uma id como parâmetro de navegação. Muito similar ao que se fez na página de leitura de artigo. app/(pages)/(private)/editArticle/[id]/page.tsx
Primeiramente gostaria de agradecer por ter disponibilizado o seu tempo para ler este tutorial e também gostaria de parabeniza-lo por ter feito este tutorial. Espero que ele tenha lhe servido e tenha sido fácil de seguir o passo a passo.
Segundo, gostria de comentar alguns pontos sobre o que acabamos de construir. Esse é o básico de um sistema de blogs e falta ainda adicionar muita coisa, como uma página pública de exibição de todos os artigos, ou uma página de registro de novos usuários, ou mesmo uma página pessoal de erro de rota 404. Caso, se durante o tutorial, você se perguntou sobre estas páginas e sentiu a sua falta, saiba que isso foi proposital. Este tutorial lhe deu experiência o bastante para ser capaz de criar essas novas áginas por você mesmo e adicionar muitas outras mais e novas funções.