next.js

Entendendo Next.js e aplicando suas funcionalidades

O que é o Next.js?

Next.js é um framework para React. O que isso quer dizer? O React é uma biblioteca Javascript para construção de interfaces e o Next é considerado um framework pois adiciona várias funcionalidades em cima do React.

Algumas funcionalidades do Next.js: renderização estática e pelo lado do servidor. Possui suporte ao Typescript e um serviço de tratamento de rotas muito interessante, que vamos detalhar mais durante esse texto.

Muitas funcionalidades são mais perceptíveis com a nossa aplicação em produção, pois o Next possui dois pontos principais em seu objetivo: tornar nossa aplicação React mais performática e a questão da indexação do conteúdo da página pelos motores de busca.

Sobre esse ponto da indexação, quando desenvolvemos nossas aplicações da maneira tradicional com React, toda nossa interface e toda chamada à API se faz pelo lado do cliente (browser).

Então, quando algum motor de busca ou crawler tentar indexar uma página feita em React, geralmente não vai esperar que nossa aplicação faça o carregamento do Javascript, chamadas à API e toda a construção da página.

Ou seja, essa busca retorna vazia ou sem as informações mais relevantes para que nossa aplicação consiga ser indexada.

Onde o Next.js se encaixa nesse cenário? Para gerar toda a página para o browser, o Next utiliza um servidor Node.js. Usa-se Node.js nesse cenário apenas por entender Javascript nativamente.

Dessa forma, o Next consegue entregar a página pronta para o Browser, ou seja, todo o HTML, CSS e Javascript. Esse comportamento chama-se Server-Side-Rendering.

o-que-e-next-js
Figura 1: Fluxo de requisição do usuário com Next.js

Com essa funcionalidade, a página é retornada para o usuário toda montada. Pensando que esse usuário seja um motor de busca, todo o conteúdo da página retorna, dispondo conteúdo para ser indexado.

Integrando o Typescript ao projeto com Next.js

Antes de mais nada, é importante entender que gerar um projeto com Next.js é bem parecido com a criação de um projeto React, porém, o comando para criar seu projeto será:

Ser você estiver utilizando o yarn:

yarn create next-app nome-do-seu-projeto

Se você estiver utilizando o npm:

npx create-next-app nome-do-seu-projeto

Após criar seu projeto, assim como no React, você verá algo como um boilerplate, com algumas pastas e arquivos.

Para integrar o Typescript em seus projetos, basta instalar os pacotes, como abaixo:

Utilizando o yarn:

yarn add typescript @types/react @types/node –D

Utilizando o npm:

npm install typescript @types/react @types/node –D

Após a instalação desses pacotes, basta alterar os arquivos para ts ou tsx. Juntamente com isso, para fazer sua aplicação rodar, basta executar o comando yarn dev ou npm run dev, dependendo do gerenciador de pacotes que você esteja utilizando.

O arquivo tsconfig.json será gerado automaticamente com todas as configurações necessárias para utilização do Typescript.

Como funciona a construção das páginas e rotas?

A figura 2 mostra a estrutura de pastas ao iniciar um projeto Next.js na sua IDE. Veja que as alterações nos arquivos para utilização do Typescript e React (tsx) estão prontas.

pastas-iniciais-ide
Figura 2: Estrutura de pastas inicial de um projeto com o Next.js

Essa vai ser basicamente a estrutura que você verá inicialmente. Uma informação importante é que a pasta com nome “pages” NÃO pode ter seu nome alterado, pois, é a partir dessa pasta que o Next fará a busca por suas páginas. Colocar essa pasta dentro de outra pasta chamada “src” é o máximo que pode ser feito, como mostra a figura 3.

pasta-src-adicionada
Figura 3: Inserindo a pasta “src” ao projeto.

Roteamento das páginas

Uma funcionalidade bem legal do Next é como ele trata o roteamento das páginas. Não é preciso realizar nenhum tipo de configuração ou alguma biblioteca para fazer o tratamento de rotas.

Nesse sentido, basta criar uma página dentro da pasta “pages” e o próprio Next.js vai entender que aquele arquivo “.tsx “ é uma rota acessável. Esse comportamento torna a criação e roteamento das páginas muito mais fácil.

Ao criar um arquivo dentro da pasta “pages”, o Next.js automaticamente irá assumir que o nome daquele arquivo é um endereço acessível da sua aplicação.

Caso seja necessário criar algum arquivo ou alguma pasta dentro dessa pasta que não será vista como um endereço, basta acrescentar o “_” antes do nome do arquivo ou pasta, assim como é com o arquivo “_app.tsx”.

Ainda dentro das funcionalidades de rotas do Next, é importante falar sobre rotas dinâmicas. Caso seja necessário passar algum parâmetro ou um nome de algum produto, por exemplo, para a rota, basta incluir dois colchetes no nome da página, como mostra a figura 4:

rotas-dinamicas-next.js
Figura 4: Utilizando rotas dinâmicas com o Next.js

Dessa forma, veja como ficou a estrutura de pastas:

estrutura-de-pastas-atual-next-js
Figura 5: Estrutura de pastas atual do projeto.

Observe que o nome entre colchetes poderia ser qualquer outro, porém, para representar o slug de algum produto de uma loja, usamos esse nome.

Para recuperar esse parâmetro, basta utilizar o useRouter() do próprio next/router, como mostra a figura 6:

recuperar-parametro-next.js
Figura 6: Recuperando um parâmetro da rota
import { useRouter } from 'next/router';
 
export default function product() {
    const router = useRouter();
 
    return <h1>{router.query.slug}</h1>
}

Tratamento de dados vindos de uma API

Em seguida, para mostrar como o Next trata requisições que vêm de uma API, vamos criar uma API de exemplo, utilizando o json-server, que já nos traz bastante funcionalidades.

Só para exemplificar, segue abaixo o código da nossa API:

{
  "categories": [
    { "id": "teclados", "title": "Teclados" },
    { "id": "notebooks", "title": "Notebooks" },
    { "id": "armazenamento", "title": "SSD" },
    { "id": "gabinetes", "title": "Gabinetes" },
    { "id": "mouses", "title": "Mouses" }
  ],
  "products": [
    { "id": 1, "title": "Teclado Mecânico", "price": 350, "category_id": "teclados", "slug": "teclado-mecanico"},
    { "id": 2, "title": "Mouse Gamer", "price": 120.9, "category_id": "mouses", "slug": "mouse-gamer"},
    { "id": 3, "title": "SSD 512GB", "price": 650.45, "category_id": "ssd", "slug": "ssd-512gb"},
    { "id": 4, "title": "Notebook Gamer", "price": 7000, "category_id": "notebooks", "slug": "notebook-gamer"},
    { "id": 5, "title": "Monitor Gamer", "price": 2000, "category_id": "monitores", "slug": "monitor-gamer"},
    { "id": 6, "title": "Gabinete Gamer", "price": 500, "category_id": "gabinetes", "slug": "gabinete-gamer"}
  ],
  "recommended": [
    { "id": 1, "title": "Teclado Mercânico", "price": 350, "category_id": "teclados", "slug": "teclado-mecanico"},
    { "id": 4, "title": "Notebook Gamer", "price": 7000, "category_id": "notebooks", "slug": "notebook-gamer"}
  ]
}

Como primeiro exemplo, vamos falar sobre o Client Side Fetching, para consumir essa API. Esse método é a maneira de buscar informações de uma API do jeito mais tradicional possível no React: usando o useEffect, por exemplo.

A estratégia de Client Side Fetching deve ser utilizada apenas quando não há a necessidade de que as informações vindas da API sejam indexadas pelos motores de busca, pois, uma chamada vinda do useEffect só será disparada no browser, ou seja, o conteúdo da página não será renderizado pelo lado do servidor. É possível observar esse comportamento desabilitando o javascript do borwser.

Com o javascript desabilitado, as informações vindas da API através do useEffect, não serão exibidas em nenhum momento. Abaixo há um código de uma implementação desse conceito:

import React, { FormEvent, useEffect, useState } from 'react';
 
import api from '../service/api';
 
interface IProducts {
    id: number;
    title: string;
}
 
export default function Home() {
    const [products, setProducts] = useState<IProducts[]>([]);
 
// Chamada à API utilizando o Client Side Fetching
    useEffect(() => {
        api.get('http://localhost:3333/products').then(response => {
          setProducts(response.data);
        })
    }, []);
 
    return (
        <div>
          <h1>Products</h1>
        
          <section>
            <ul>
              {products.map(product => {
                  return (
                      <li key={product.id}>
                        {product.title}
                      </li>
                    )
                })}
            </ul>
          </section>
        </div>
      )
}
 
export default Home;

Na figura 7 você verá o resultado dessa chamada à nossa API:

cahamda-api-next.js
Figura 7: Chamada à API utilizando o Client Side Fetching

Como é possível verificar, essa chamada para o nosso backend funciona normalmente utilizando o Next.js, da mesma forma do que quando utilizamos o React sem esse framework. Mas, o que eu gostaria de mostrar é o comportamento a seguir. Verifiquem, na figura 8, o resultado se nós desabilitamos o Javascript do nosso navegador e damos um refresh na página:

desabilitar-javascript-next-js
Figura 8: Sem retorno visual desabilitando o Javascript do browser.

Observe que nenhuma informação de nossa chamada utilizando o useEffect foi exibida em tela. Como explicado anteriormente, essa chamada será executada apenas pelo lado do cliente.

Existe algum problema com esse tipo de chamada? Claro que não, porém, é importante apenas entender as estratégias de renderização disponíveis e em quais momentos elas podem se aplicar. Nesse caso, se for necessária a indexação por motores de busca, essa não será a melhor estratégia.

Server Side Rendering

Como já adiantado anteriormente, uma outra maneira de consumo de dados em uma API no Next.js, é utilizando o Sever Side Rendering. Essa estratégia nos oferece a possibilidade de apresentar todos os dados de uma só vez para o cliente. Isso implica em carregar todos os dados primeiramente no lado do servidor, nesse caso, utilizando um servidor Node.js (lembra o fluxo que a gente apresentou no início desse artigo?), e assim todo o conteúdo da página estará disponível para ser exibido para o cliente de uma só vez.

Só para exemplificar melhor esse comportamento, vamos executar nosso json-server utilizando um parâmetro “-d 2000”, para simular um delay de 2 segundos em nossa chamada ao backend.

O comando ficará assim:

npx json-server server.json -p 3333 -d 2000

Com esse delay na chamada ao nosso backend, vai ser possível observar que o título da nossa página, no caso, “Products”, exibirá 2 segundos antes do conteúdo da chamada para a nossa API.

Se desabilitarmos o Javascript do navegador, vamos ver que o conteúdo nunca será exibido, como exemplificado na seção anterior. Para contornar esse comportamento, o Next.js oferece a funcionalidade de Server Side Rendering.

Como funciona na prática o Server Side Rendering em Next.js

Para começar, vamos tirar toda a chamada feita pelo useEffect do nosso código:

import React from 'react';
 
import api from '../service/api';
 
interface IProducts {
    id: number;
    title: string;
}
 
export default function Home() {
    return (
      <div>
        <h1>Products</h1>
      
        <section>
          <ul>
            {products.map(product => {
                return (
                    <li key={product.id}>
                      {product.title}
                    </li>
                  )
              })}
          </ul>
        </section>
      </div>
    )
  }

Agora vamos utilizar uma função chamada getServerSideProps. Ao utilizarmos essa função, o Next.js, fará a pré renderização da página usando o retorno dessa função. É possível acessar qualquer retorno dessa função em nosso componente, através de propriedades.

Vamos transferir nossa chamada à API para essa função, fazendo algumas alterações para utilizá-la com o formado async await, que é uma sintaxe mais moderna. Além da chamada à API, como estamos utilizando Typescript, vamos tipar essa função para que possamos retornar as propriedades que precisamos. Por isso vamos importar, de dentro do Next.js, a tipagem GetServerSideProps e no retorno da função já vamos ter disponível nossas props.

export const getServerSideProps: GetServerSideProps = async () => {
  const response = await api.get('http://localhost:3333/products');
 
  const products = await response.data;
 
  return {
    props: {
      
    }
  }
}

Porém, se buscarmos alguma informação à ser retornada dessas props não vamos encontrar nada. Por isso, vamos criar uma interface chamada ProductsProps, para explicitarmos qual ou quais propriedades queremos retornar dessa função.

interface IProducts {
    id: number;
    title: string;
}
 
interface ProductProps {
  products: IProducts[];
}

Essa propriedade será um array de produtos e será o parâmetro de retorno de GetServerSideProps.

export const getServerSideProps: GetServerSideProps<ProductProps> = async () => {
  const response = await api.get('http://localhost:3333/products');
 
  const products = await response.data;
 
  return {
    props: {
      
    }
  }
}

Propriedades para retorno

Quando fazemos isso, já temos disponível nossas propriedades para retorno, como mostra a figura 9:

retorno-funcao-gerserversideprops
Figura 9: Exibindo as propriedades de retorno da função getServerSideProps.

Para utilizarmos essa propriedade, precisamos passar como parâmetro para nosso componente, que nesse caso é o Home. Precisamos tipar essa propriedade, para deixar claro que tipo de parâmetro estamos repassando para nosso componente.

export default function Home({ products }: ProductProps) {
    return (
      <div>
        <h1>Products</h1>
      
        <section>
          <ul>
            {products.map(product => {
                return (
                    <li key={product.id}>
                      {product.title}
                    </li>
                  )
              })}
          </ul>
        </section>
      </div>
    )
}

Com isso podemos observar o comportamento da página bem diferente. Ao realizarmos a chamada para nossa API, vemos que toda a página demora os 2 segundos, ou seja, toda a página carrega de uma só vez. Esse comportamento é o que desejamos quando precisamos indexar nossas páginas para algum motor de busca, como o Google, por exemplo.

Com a página sendo gerada pelo lado do servidor, nossa aplicação não vai mais depender do Javascript estar habilitado. Tudo já foi criado e passado para o cliente. Observe o comportamento no vídeo seguinte:

Apenas uma observação. É recomendado utilizar esse método apenas quando é necessário gerar algum conteúdo dinâmico para o cliente ou que precisa estar disponível para os motores de busca.

Você vai precisar avaliar a necessidade da sua aplicação e qual experiência você quer oferecer para seus usuários, pois, como é possível observar, toda a página demorou os 2 segundos para aparecer.

Static Site Generation

Um outro método de data fetching dentro do Next.js é o Static Site Generation. Traduzindo ao pé da letra, seria algo como “Geração de Sites Estáticos”. Basicamente, páginas estáticas, são páginas que não terão atualizações constantes. 

Onde seria aplicável ou em qual cenário seria possível obter algum benefício desse método? Imagina que você possui um blog ou um ecommerce que em determinada página não precisa de atualização constante.

O comportamento dela seria algo como, fazer uma chamada à API e depois exibir aquele conteúdo para todos os usuários por um determinado tempo sem atualização. 

Após a página ser carregada ou ter feito alguma requisição ao banco de dados, nenhuma outra chamada ao banco ou qualquer outra requisição à API será feita até o próximo “ciclo” de atualização, que é totalmente configurável, como veremos mais adiante.

Dessa forma, a página será servida de forma totalmente estática. Há um ganho relevante de performance utilizando essa forma de servir páginas.

E como implementar o site estático com Next.js

O princípio para implementação é quase igual ao Server Side Rendering. Para não apagar o conteúdo criado até aqui, pois, ficará disponível para você baixar e consultar no GitHub, vamos criar um outro componente para exemplificar o comportamento da nossa página.

Vamos criar um componente chamado “categories”. Esse componente exibirá todas as categorias de produtos cadastrados em nossa API. Acredito que não seja necessário explicar detalhadamente o código abaixo, pois, é basicamente o mesmo código apresentado na seção anterior, porém, agora estamos chamando uma função chamada getStaticProps, que possui o tipo GetStaticProps, importado de dentro de Next.js.

import React from 'react';
import { GetStaticProps } from 'next';
 
import api from '../service/api';
 
import { Container } from '../../styles/Dashboard/styles';
 
interface ICategories {
    id: number;
    title: string;
}
 
interface CategoryProps {
  categories: ICategories[];
}
 
export default function categories({ categories }: CategoryProps) {
    return (
        <Container>
            <h1>Categories</h1>
        
            <section>
            <ul>
                {categories.map(categorie => {
                    return (
                        <li key={categorie.id}>
                        {categorie.title}
                        </li>
                    )
                })}
            </ul>
            </section>
        </Container>
    )
}
 
export const getStaticProps: GetStaticProps<CategoryProps> = async () => {
    const response = await api.get('http://localhost:3333/categories');
    const categories = await response.data;
 
    return {
        props: {
            categories,
        }
    }
}

Após a implementação da nossa função getStaticProps, nossa página categories está sendo gerada totalmente de forma estática. Em ambiente de desenvolvimento é difícil reproduzir esse comportamento, pois, estamos utilizando um delay de 2 segundos em nossa chamada à API. Mas em ambiente de produção, essa página seria carregada totalmente de forma estática, gerando um ganho de performance muito significativo.

Na figura 10 podemos ver nossa página.

retorno-api-next.js
Figura 10: Retorno da nossa API utilizando o método Static Site Generation

Ok, já temos nossa página estática sendo gerada, mas e se eu quiser que o conteúdo dessa página seja atualizado com alguma informação ou que alguma informação seja retirada? Nesse momento, caso alguma informação, por exemplo, seja acrescentada à nossa API, essa página não seria atualizada em tempo real, lembre-se que ela está sendo gerada estaticamente.

Por exemplo, ao acrescentar mais uma categoria em nossa API, nossa página não faria uma nova chamada a API para atualizar o conteúdo exibido e permaneceria desatualizada em relação ao conteúdo armazenado no banco de dados. A figura 11 mostra que acrescentamos mais uma categoria em nossa API:

acrescentando-item-api-next.js
Figura 11: Acrescentando um item na API

GetStaticProps

Para contornar esse comportamento, a função do tipo GetStaticProps, oferece uma propriedade chamada “revalidate”. Essa propriedade é utilizada para informar à nossa função quando o conteúdo da página deve ser atualizado em segundos. Por exemplo, no código a seguir, inserimos 5, para indicar que queremos que o conteúdo da página seja revalidado ou atualizado a cada 5 segundos.

export const getStaticProps: GetStaticProps<CategoryProps> = async () => {
    const response = await api.get('http://localhost:3333/categories');
    const categories = await response.data;
 
    return {
        props: {
            categories,
        },
        revalidate: 5,
    }
}

Em ambiente de desenvolvimento é difícil reproduzir o comportamento do revalidate, porém, em um ambiente de produção, o Next.js fará a atualização da página totalmente de forma automática, sem qualquer outra ação nossa. Isso é extremamente útil no dia a dia para ganhar performance em nossas aplicações. 

Só para se ter uma ideia de performance, imagina que sua aplicação tenha 1000 acessos em um intervalo de sessenta segundos. Ao invés de mil chamadas à sua API, seriam apenas 12.

É um ganho gigante em performance. Ressaltando que, é sempre importante avaliar as necessidades dos usuários que vão utilizar sua aplicação.

Conclusão

Por fim, nesse artigo tivemos uma visão geral sobre o que é o Next.js e onde ele pode se encaixar em nossos projetos. O Next.js é uma ferramenta muito versátil e pode ser utilizada na grande maioria de seus projetos. 

Também apresentamos algumas funções e seus comportamentos, como o Client Side Fetching, que apesar do nome bonito, é uma chamada à API da forma tradicional feita no React.

O Server Side Rendering, que é a geração de conteúdo sendo feita pelo lado do servidor Node.js que o Next.js possui, que apresenta a página totalmente pronta de uma só vez para o cliente.

E por último, foi apresentado a função Static Site Generation, que gera as páginas de forma estática, oferecendo um ganho de performance bastante interessante.

Para extrair ao máximo de benefícios dos recursos oferecidos pelo Next.js, é sempre importante avaliar o cenário em que sua aplicação vai ser utilizada e se algum dos recursos aqui apresentados faz sentido e em quais momentos. Nesse sentido, o bom senso e um bom planejamento são sempre bem-vindos nessas horas.

Repositório do GitHub: https://github.com/AlbertDev33/Artigo-para-geekhunter

Referências: Next.js by Vercel – The React Framework

Leia mais sobre as funções apresentadas nesse artigo aqui: Basic Features: Data Fetching | Next.js

cta-banner-next.js
Compartilhar
You May Also Like