Criando Um Site Estático Com Next
Este post é uma continuação das melhorias implementadas no blog. Para saber mais, confira o primeiro artigo sobre como melhorei meu blog.
Após automatizar a criação de posts no blog usando arquivos Markdown, percebi que também precisava automatizar a criação de rotas.
Antes para cada novo post, eu tinha que criar manualmente uma nova rota. Isso era muito chato, pois tinha que copiar e colar repetidamente, além de criar muitas pastas no projeto.
Como seria bom se tivesse uma maneira de gerar rotas dinâmicas automaticamente, sem ter de fazer isso manualmente...
Senhoras e senhores, lhes apresento o Slug.
Slug
Antes de explicar o Slug é preciso explicar como funciona o sistema de rotas do Next.
O sistema de rotas do Next é baseado em arquivos, onde cada arquivo na pasta app corresponde a uma rota acessível. Se eu tivesse que criar uma rota para cada novo post (Inglês e Português) manualmente eu estaria muito triste (eu realmente estava, pois era exatamente isso que eu estava fazendo 😔).
É aqui que entra o Slug. O Slug é uma forma dinâmica para criação de rotas no Next. Em vez de criar uma rota estática para cada post, podemos utilizar Slug. Por exemplo, ao invés de ter uma rota como /posts/my-new-post, podemos usar /posts/[slug], onde [slug] é substituído dinamicamente pelo identificador único do post.
Ok, mas como fazemos isso?
Estrutura do blog
Meu blog tem algumas pastas importantes. A primeira é a ./content/posts, onde estão os arquivos Markdown que serão convertidos em páginas HTML. Um exemplo de como essa estrutura é:
./content/post/creating-my-personal-site/
Dentro da pasta creating-my-personal-site eu tenho dois arquivos Markdown, um em Inglês e outro em Português:
- creating-my-personal-site.en.md
- criando-meu-site-pessoal.pt-br.md
Cada post do blog terá sua própria pasta dentro de ./content/posts, contendo dois arquivos. O nome de cada arquivo será usado como o Slug e o título da página.
Rotas
Dentro da pasta app temos algumas rotas:
- / é a rota index do site
- /blog/en é a rota das listagens de posts em Inglês
- /blog/pt-br é a rota das listagens de posts em Português
- /blog/en/[slug] é a rota dinâmica para o post em Inglês
- /blog/pt-br/[slug] é a rota dinâmica para o post em Português
O que acontece de forma simples é: a listagem de postagens itera sobre todos os arquivos na pasta ./content/posts correspondente ao idioma da página. De cada arquivo, são recuperados o título, a data da postagem e o nome do arquivo.
O código que eu uso para buscar esses dados é:
import fs from "fs"; import matter from "gray-matter"; import { ENCODING_UTF8 } from "@/utils/constants"; export default function getPostMetadata(basePath, language) { const folder = basePath + "/"; const postFolders = fs.readdirSync(folder); const posts = postFolders.map((postFolder) => { const files = fs.readdirSync(`${basePath}/${postFolder}/`); const filename = files.find((file) => file.includes(`.${language}.md`)); const fileContent = fs.readFileSync( `${basePath}/${postFolder}/${filename}`, ENCODING_UTF8 ); const { title, date } = matter(fileContent).data; return { title, date, slug: filename.replace(`.${language}.md`, ""), }; }); return posts.sort((a, b) => new Date(b.date) - new Date(a.date)); }
O retorno dessa função é esse array:
[ { title: "Creating a Static Site with Next", date: "2024-06-15", slug: "static-site-generation-with-next", }, { title: "Improving My Blog with Gray-matter and React-markdown", date: "2024-06-14", slug: "improving-my-blog", }, { title: "How to Host a Website", date: "2024-05-08", slug: "how-to-hosting-a-website", }, { title: "Creating My Personal Site", date: "2024-04-23", slug: "creating-my-personal-site", }, ];
Com essas três informações, podemos exibir uma lista de postagens, ordenadas da mais recente para a mais antiga. O nome do arquivo é o Slug, permitindo que possamos criar um link usando o Slug correspondente.
Mas, se há 4 posts no exemplo acima, como o Next associa cada Slug ao respectivo post? Em outras palavras, como o Next recupera o conteúdo e associa ao Slug correspondente?
Simples, usando o Static Site Generation.
Static Site Generation (SSG)
O Static Site Generation (SSG) é um recurso do Next que permite pré-renderizar páginas HTML no momento do build, ao invés de fazer isso a cada requisição.
Mas como fazemos isso?
Para ilustrar, vamos utilizar a rota em Inglês para as postagens: app/blog/en/[slug]/page.js
Dentro do page.js, precisamos exportar uma função assíncrona chamada generateStaticParams para definirmos os parâmetros que serão pré-renderizados. Essa função define quais URLs dinâmicas devem ser pré-renderizadas no momento do build.
O exemplo utilizado aqui no Blog:
export const generateStaticParams = async () => { const posts = getPostMetadata(CONTENT_FOLDER, EN_LANGUAGE); return posts.map((post) => ({ slug: post.slug })); };
Repare que eu uso a mesma função da listagem de postagem, porém retornando apenas o Slug. Dessa forma, todos os Slugs em Inglês vão ser pré renderizados. O mesmo processo acontece no caminho app/blog/pt-br/[slug]/page.js, só que para o idioma Português.
Assim, o Slug pode ser acessado como uma propriedade do próprio componente page.js.
Lembra que o Slug é o nome do arquivo Markdown que contém o conteúdo da postagem? Para recuperar esse conteúdo, basta usar o Gray-matter que explicamos no post anterior.
const DynamicPost = (props) => { const slug = props.params.slug; const { data, content } = getPostContent(slug); return <Post data={data} content={content} />; }; export default DynamicPost;
Usando essa estratégia todas as páginas do blog são pré renderizadas no build, por isso que é tão rápido.
Outro ponto é que o blog fica bastante otimizado, como podemos ver no lighthouse:
Essas foram todas as mudanças que foram feitas no blog. Sim, deu bastante trabalho pra fazer esses ajustes, mas agora sinto que o blog está ficando cada vez mais robusto.
Se quiser se aprofundar mais, o incrível vídeo do Smoljames explica em detalhes o uso de SSG. Utilizei bastante o código dele como referência.