Guia básico para iniciar com Next.js parte II

Guia básico para iniciar com Next.js parte II

Se você chegou até aqui significa que gostou da primeira parte do Guia básico para iniciar com Next.Js e quer aprofundar seu conhecimento.

A aplicação que estamos construindo é um blog. Já temos a estrutura de layout pronta, mas ainda sem nenhum conteúdo.

Então, agora você aprenderá a buscar dados externos, implementá-los na sua aplicação, além de criar algumas rotas dinâmicas.

Vamos começar!

Pre-rendering e Data fetching

Antes de falar sobre data fetching (que é o request que fazemos na api para poder consumir determinado dado), vamos falar sobre o pre-rendering, ou renderização prévia.

Renderização prévia significa que o Next vai pré renderizar o HTML de todas as páginas da sua aplicação, ao invés de deixar que o JavaScript renderize tudo no client-side.

Cada página gera um HTML com o mínimo possível de JavaScript associado e então, quando a página é carregada esse código JavaScript roda e deixa a página totalmente interativa. Esse processo é chamado de hydration.

Como benefício teremos aumento de performance e SEO.

Existe duas maneiras de pré-renderização que o Next.js nos oferece:

  • Static Generation: é o método de pré-renderização que gera o HTML no momento do build do projeto, e então o HTML é reutilizado a cada request. É o método mais recomendado, mas há algumas exceções como, por exemplo, sites que precisam de atualização instantânea de dados.
  • Server-side Rendering: é o método de pré renderização que gera o HTML em cada request.

É interessante dizer que o Next permite a escolha de qual método queremos utilizar ou, construir uma aplicação híbrida utilizando ambos. Para a nossa aplicação, utilizaremos o Static Generation.

Static Generation

O método Static Generation pode ser utilizado quando há uso de dados externos de API, e quando não há também.

Para esse processo existe uma função assíncrona no Next chamada getStaticProps que é executada durante o build e dentro dessa função realizamos uma chamada de API para conseguir os dados necessários. Com esse resultado em mãos, passaremos ele como props para a página onde será utilizado.

Vamos usar essa função no nosso projeto e ver como realmente funciona! Mas primeiro, algumas configurações de arquitetura precisam ser feitas.

Criando a arquitetura do blog

Faremos um pouco diferente do “comum”: ao invés de consumir os posts de alguma API externa nós vamos armazenar os posts em um markdown local na nossa aplicação.

Primeiro, vamos criar na raiz do projeto uma pasta chamada posts (cuidado para não confundir com a pasta que temos dentro de pages/posts), e dentro desta pasta vamos criar os arquivos:

  • pre-rendering.md
  • ssg-ssr.md

Sua organização de pastas deve ficar assim:

Agora, dentro de posts/pre-rendering.md coloque o seguinte conteúdo:

---
title: 'Duas formas de pré-renderização'
date: '2022-10-25'
---

O Next.js tem duas formas de pré-renderização: **Static Generation** e **Server-side Rendering**. 
A diferença está em **quando** ele gera o HTML para uma página.

- **Static Generation** é o método de pré-renderização que gera o HTML no **tempo de compilação**. O HTML pré-renderizado é então _reutilizado_ em cada solicitação.
- **Renderização do lado do servidor** é o método de pré-renderização que gera o HTML em **cada solicitação**.

É importante ressaltar que o Next.js permite que você **escolha** qual formulário de pré-renderização usar para cada página.
Você pode criar um aplicativo Next.js "híbrido" usando geração estática para a maioria das páginas e renderização do lado do servidor para outras.

E dentro de posts/ssg-ssr.md:

---
title: 'Quando usar geração estática vs.s. Renderização do lado do servidor'
date: '2020-01-02'
---

Recomendamos usar **Static Generation** (com e sem dados) sempre que possível, pois sua página pode ser criada uma vez e servida por CDN, o que torna muito mais rápido do que ter um servidor para renderizar a página em cada solicitação.

Você pode usar a geração estática para muitos tipos de páginas, incluindo:

- Páginas de marketing
- Postagens no blog
- Listas de produtos de comércio eletrônico
- Ajuda e documentação

Você deve se perguntar: "Posso pré-renderizar esta página **antes** da solicitação de um usuário?" Se a resposta for sim, você deve escolher Geração Estática.

Por outro lado, a geração estática **não** é uma boa ideia se você não puder pré-renderizar uma página antes da solicitação de um usuário. Talvez sua página mostre dados atualizados com frequência e o conteúdo da página mude a cada solicitação.

Nesse caso, você pode usar **Renderização do lado do servidor**. Será mais lento, mas a página pré-renderizada estará sempre atualizada. Ou você pode pular a pré-renderização e usar JavaScript do lado do cliente para preencher os dados.

Você deve ter reparado que nos dois arquivos temos uma seção de metadados contendo  title e date. Isso se chama YAML Front Matter. Esses metadados podem ser analisados usando a biblioteca gray-matter.

💡YAML: é posicionado no topo da página e utilizado para atribuir metadados para a página e seu conteúdo.

Para instalar esse biblioteca rode no seu terminal:

yarn add gray-matter

Em seguida vamos criar uma função que irá:

  • Analisar cada arquivo markdown e retornar título, data e nome do arquivo
  • Listar os dados da página organizados por data.

Crie uma pasta chamada lib  na raiz do projeto e dentro um arquivo posts.js.

E dentro desse arquivo, teremos as seguintes configurações:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getSortedPostsData() {
  // Pegue o nome dos arquivos dentro de /posts
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map((fileName) => {
    // Remova ".md" do nome do arquivo para pegar o seu id
    const id = fileName.replace(/\\.md$/, '');

    // Leia o arquivo markdown como string
    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');

    // Use gray-matter para analisar os metadados da seção
    const matterResult = matter(fileContents);

    // Combine os dados com o id
    return {
      id,
      ...matterResult.data,
    };
  });
  // Organize os posts pela data
  return allPostsData.sort(({ date: a }, { date: b }) => {
    if (a < b) {
      return 1;
    } else if (a > b) {
      return -1;
    } else {
      return 0;
    }
  });
}

💡 Dica: não se assuste! Você não precisa entender tudo em detalhes dessa configuração para aprender o Next.js! Vamos focar no essencial :)

Agora que já temos os nossos dados da maneira que precisamos, vamos utilizá-los no blog junto com a função que falamos anteriormente getStaticProps().

Implementando o getStaticProps()

No arquivo /pages/index.js temos que importar a função de configuração getSortedPostsData do arquivo que criamos acima.

Após abrir o arquivo, adicione o bloco de código a segur no início do arquivo antes da função Home:

import { getSortedPostsData } from '../lib/posts';

export async function getStaticProps() {
  const allPostsData = getSortedPostsData();
  return {
    props: {
      allPostsData,
    },
  };
}

Aqui, estamos retornando allPostsData dentro de um objeto props, que serão passado para o componente Home:

export default function Home({ allPostsData }) {...}

Agora, para acessar os posts dentro da Home, vamos adicionar outra tag <section> abaixo da sessão com a sua apresentação:

import Head from 'next/head'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'
import { getSortedPostsData } from '../lib/posts'

export async function getStaticProps() {
  const allPostsData = getSortedPostsData()
  return {
    props: {
      allPostsData,
    },
  }
}

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>

      // sessão já existente
      <section className={utilStyles.headingMd}>
        <p>[Quem é você]</p>
        <p>
          (Esse é um website de exemplo - você pode ver mais detalhes na própria
          documentação do <a href="<https://nextjs.org/learn>"> Next.js</a>.)
        </p>
      </section>

      // nova sessão
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  )
}

Parabéns! Você acabou de fazer um request para um arquivo externo de dados e pré-renderizou a página com esses dados. 🥳

Acesse seu navegador em http://localhost:3000 e veja seu resultado:

Esse foi o uso do getStaticProps, lembrando que, além do consumo de dados vindos do file system (markdown), também podemos fazer request a API externa e banco de dados. Isso só é possível porque o request acontece no lado do servidor.

Lembre-se:

  • Em ambiente de desenvolvimento (yarn dev), o getStaticProps executa em todo request.
  • Em produção, o getStaticProps executa no momento do build.

💡Dica: getStaticProps só pode ser exportado por uma página. Você não pode exportá-lo em algum arquivo que não seja uma página.

Até aqui já criamos nossa arquitetura e a nossa Home do blog, mas ainda não temos a página de cada post.

No próximo bloco iremos aprender como gerar páginas estáticas com rotas dinâmicas usando o getStaticPaths, como conectar rotas dinâmicas e mais alguns detalhes.

Vamos lá!

Rotas dinâmicas

Anteriormente nós construimos uma página que depende de dados externos e utilizamos o getStaticProps para requisitar esses dados. Agora, vamos criar uma página onde a rota também dependerá de dados externos.

A ideia é criar uma rota onde o path seja /posts/<id> sendo o <id> o nome do arquivo markdown que criamos, ou seja, teremos /posts/ssg-ssr e /posts/pre-rendering.

Para gerar essas páginas onde o <id> será dinâmico, seguiremos os seguintes passos:

  • Crie uma página dentro de /pages/posts/[id].js.
  • Essa página deve conter:

1) Um componente React para renderizar a página.

2) getStaticPaths que retorna um array com os possíveis IDs.

3) getStaticProps para requisitar os dados dos posts com ID.

Vamos seguir juntos passo a passo para e entender ainda mais estes conceitos.

Como criar páginas estáticas com rotas dinâmicas

Para começar, vamos criar dentro de /pages/posts um arquivo chamado [id].js e excluir aquele arquivo que criamos no artigo anterior chamado first-post.js.

Dentro desse arquivo coloque a estrutura inicial:

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

Agora, vá até  o arquivo /lib/posts.js e adicione a função getAllPostIds no final do arquivo. Essa função vai retornar um array de objetos com os nomes dos arquivos que criamos sem o .md no final:

export function getAllPostsIds() {
  const fileNames = fs.readdirSync(postsDirectory);

  return fileNames.map((fileName) => {
    return {
      params: {
        id: fileName.replace(/\\.md$/, ''),
      }
    }
  })
}

// O resultado deverá ser:
  // [
  //   {
  //     params: {
  //       id: 'ssg-ssr'
  //     }
  //   },
  //   {
  //     params: {
  //       id: 'pre-rendering'
  //     }
  //   }
  // ]

Agora vamos importar a função getAllPostsIds e usá-la dentro de getStaticPaths no arquivo [id].js.

No início do arquivo, antes da função Post coloque:

import { getAllPostIds } from '../../lib/posts';

export async function getStaticPaths() {
  const paths = getAllPostsIds();
  return {
    paths,
    fallback: false,
  };
}

Dentro da constante paths teremos o resultado que esperamos de getAllPostIds(). Sobre o fallback, falaremos mais tarde.

Quase pronto! Agora vamoa implementar o getStaticProps.

Precisamos fazer uma requisição para obter os dados necessários do post com seu determinado id. Para isso, vamos no arquivo /lib/posts.js mais uma vez, e adicionar a função getPostData no final do arquivo. Essa função nos trará o post baseado em seu id.

export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // Use gray-matter para analisar a seção de metadados
  const matterResult = matter(fileContents);

  // Aqui fazemos a junção dos dados com o id
  return {
    id,
    ...matterResult.data,
  };
}

E então, em pages/posts/[id].js vamos chamar essa nova função e alterar o corpo do componente com as informações que conseguimos.

Sua página deverá ficar assim:

import Layout from '../../components/layout'

import { getAllPostsIds, getPostData } from '../../lib/posts'

export async function getStaticPaths() {
  const paths = getAllPostsIds()
  return {
    paths,
    fallback: false,
  }
}

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id)
  return {
    props: {
      postData,
    },
  }
}

export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </Layout>
  )
}

Em seu navegador acesse:

O resultado deverá ser:

Sucesso! Você acabou de construir sua primeira rota dinâmica! 😊

Mas ainda não renderizamos o conteúdo da página. Vamos fazer a seguir!

Renderizando o conteúdo do blog

Para renderizar na página o conteúdo do blog que vem de um markdown, usaremos uma biblioteca chamada remark.

Para instalar:

yarn add remark remark-html

Abra lib/posts.js e adicione no topo da página:

import { remark } from 'remark';
import html from 'remark-html';

E atualize a função getPostData pelo seguinte código:

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')

  // Use gray-matter para analisar a seção de metadados
  const matterResult = matter(fileContents)

  // O remark converte o markdown em uma string HTML
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content)
  const contentHtml = processedContent.toString()

  // Aqui fazemos a junção dos dados com o id e com o HTML
  return {
    id,
    contentHtml,
    ...matterResult.data,
  }
}

💡Repare que alteramos a função para uma função assíncrona utilizando o async, isso porque o remark precisa usar await. Async/Await nos possibilita requisitar dados de forma assíncrona!

Sendo assim, precisamos atualizar nossa função getStatisProps em pages/posts/[id].js para também fazer uso do await quando chamar a função getPostData:

export async function getStaticProps({ params }) {
  const postData = **await** getPostData(params.id)
  return {
    props: {
      postData,
    },
  }
}

E depois, atualize o corpo do componente para renderizar o contentHtml usando o dangerouslySetInnerHTML (clique para saber mais):

export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
      <br />
      <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
    </Layout>
  );
}

Visitando a página dos posts, o resultado será:

Incrível, né! Poucas linhas de código e um resultado muito bom! Mas ainda podemos deixar  melhor.

Melhorando a página de posts

  • Adicionando a tag <title>

Como falamos no artigo anterior, o Next fornece alguns componentes, como o <Head> por exemplo. Vamos adicionar esse tag ao título do nosso blog em pages/posts/[id].js:

// Lembre-se de importar o componente
import Head from 'next/head';

export default function Post({ postData }) {
  return (
    <Layout>
      {/* Adicione a tag <Head> */}
      <Head>
        <title>{postData.title}</title>
      </Head>

			{/* Mantenha o restante do código */}
				{postData.title}
    </Layout>
  );
}

Repare no nome da sua aba no navegador. Além dessa melhora visual, a tag <Head> nos permite adicionar outras metatags que ajudam com performance de SEO. Caso não se lembre, falamos sobre isso no artigo anterior que você pode ver aqui.

  • Formatando a data

No nosso blog estamos exibindo a data da seguinte maneira: 2022-10-25. Um pouco ruim de ler, mas iremos isso melhorar isso utilizando uma biblioteca chamada date-fns.

Para instalar:

yarn add date-fns

Agora, na raiz do projeto, crie uma pasta chamada utils. Geralmente, nessa pasta criamos arquivos para salvar algumas funções ou constantes que serão bem usados em toda aplicação.

Dentro de utils crie date.js e adicione o código:

import { parseISO, format } from 'date-fns'

export default function Date({ dateString }) {
  const date = parseISO(dateString)
  return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
}

E dentro de pages/posts/[id].js importe a função que acabamos de criar e vamos utilizar onde temos a data do nosso arquivo:

// Import
import Date from '../../utils/date'

export default function Post({ postData }) {
  return (
    <Layout>
      {/* Mantenha o código existente */}

      {/* Substitua {postData.date} por isso */}
      <Date dateString={postData.date} />

      {/* Mantenha o restante do código */}
    </Layout>
  );
}

Se você acessar http://localhost:3000/posts/pre-rendering, verá que a data agora está no formato “October 25, 2021”.

💡Desafio: Acesse a documentação do date-fns e procure por locale. Tente integrar na sua função e mudar a língua da sua data.

  • Adicionando CSS

Finalmente, vamos adicionar alguns estilos na página usando o arquivo que criamos anteriormente em styles/utils.module.css.

Abra página de posts, importe o arquivo CSS e substitua o conteúdo do componente por:

import utilStyles from '../../styles/utils.module.css'

export default function Post({ postData }) {
  return (
    <Layout>
      <Head>
        <title>{postData.title}</title>
      </Head>
      <article>
        <h1 className={utilStyles.headingXl}>{postData.title}</h1>
        <div className={utilStyles.lightText}>
          <Date dateString={postData.date} />
        </div>
        <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
      </article>
    </Layout>
  )
}

Agora sim, temos uma página bem mais bonita!

Melhorando a página inicial

Até aqui trabalhamos na página de posts, desde a requisição de dados até o seu estilo. Agora, vamos focar na página inicial e deixar ela mais bonita e funcional.

  • Adicionando a tag <Link>

Assim como o componente <Head>,também existe o componente <Link> que vai funcionar como uma anchor tag (<a>).

Vamos importar esse componente no início do arquivo em pages/index.js e adicionar ao título do post. Assim conseguiremos navegar entre páginas:

import Link from 'next/link'

<li className={utilStyles.listItem} key={id}>
  <Link href={`/posts/${id}`}>
    <a>{title}</a>
  </Link>
</li>
  • Formatando a data

Lembra da função que criamos em utils/date.js? Também será útil para essa página!

import Date from '../utils/date'

<li className={utilStyles.listItem} key={id}>
  <small className={utilStyles.lightText}>
    <Date dateString={date} />
  </small>
</li>

O código final ficará assim:

import Head from 'next/head'
import Link from 'next/link'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'
import { getSortedPostsData } from '../lib/posts'
import Date from '../utils/date'

export async function getStaticProps() {
  const allPostsData = getSortedPostsData()
  return {
    props: {
      allPostsData,
    },
  }
}

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>

      <section className={utilStyles.headingMd}>
        <p>[Quem é você]</p>
        <p>
          (Esse é um website de exemplo - você pode ver mais detalhes na própria
          documentação do <a href="<https://nextjs.org/learn>"> Next.js</a>.)
        </p>
      </section>

      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              <Link href={`/posts/${id}`}>
                <a>{title}</a>
              </Link>
              <br />
              <small className={utilStyles.lightText}>
                <Date dateString={date} />
              </small>
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  )
}

E a sua página:

Muito legal, né?

Estamos quase finalizando, mas antes quero falar sobre mais alguns pontos das rotas dinâmicas no Next.

Detalhes sobre rotas dinâmicas

  • Requisição de dados de API externa ou banco de dados

Assim como a função getStaticProps, getStaticPaths também pode fazer requisições para qualquer tipo de fonte de dados.

No nosso exemplo, a função getAllPostsIds poderia fazer uma requisição externa da seguinte forma:

export async function getAllPostIds() {
  **const res = await fetch('..');**
  const posts = await res.json();
  return posts.map((post) => {
    return {
      params: {
        id: post.id,
      },
    };
  });
}

Bem simples! No seu próximo projeto você pode utilizar esse exemplo 😊

  • Fallback

Você se lembra que lá em cima, quando criamos a função getStaticPaths utilizamos um fallback: false? Isso significa que, caso você acesse uma rota que não existe dentro de getStaticPaths o resultado será uma página 404 que já vem pronta por default com o Next, mas você também pode criar uma em pages/404.js.

Se o resultado do fallback for blocking, as novas rotas serão renderizadas no lado do servidor utilizando o getStaticProps e armazenadas em cache para caso isso ocorra no futuro.

Isso vai um pouco além da nossa introdução ao Next, mas caso você queira entender mais pode visitar a documentação oficial aqui.

Se você quiser acessar o Next.js router, você pode importar o hook useRouter de next/router e com ele fazer funções do tipo:

const handleClick = (e) => {
    router.push('/posts/minha-pagina')
  }

Conclusão

Ufa! Acabamos a parte II, e você conseguiu criar uma aplicar em Next.js aplicando os principais, e mais importantes, conceitos!

Espero que tenha gostado e que a partir de agora você queira aprender ainda mais desse universo.

Caso tenha dúvidas, só entrar em contato pelo e-mail (malone.nykolle@gmail.com) ou me procurar no Linkedin!

Muito obrigada por ter ficados até aqui!

Valeu! 😊

⚠️
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.Jueves, 5 de enero