Skip to main content

Blog

App enxuta de blog em Nextra/Next.js/TypeScript

ToDo

Setup

🪜 Passo 0 – Criar app Next.js para blog

pnpm create next-app apps/blog --typescript --eslint=false

🚀 Passo 1 – Instalar o Nextra

No diretório da app (apps/blog):

pnpm add nextra nextra-theme-blog shiki

⚙️ Passo 2 – Add Next.js config

No arquivo apps/docs/next.config.ts, substitua tudo por:

import nextra from 'nextra'

// Set up Nextra with its configuration
const withNextra = nextra({
// ... Add Nextra-specific options here
})

// Export the final Next.js config with Nextra included
export default withNextra({
// ... Add regular Next.js options here
turbopack: {
resolveAlias: {
// Path to your `mdx-components` file with extension
'next-mdx-import-source-file': './src/mdx-components.ts'
}
}
})

Passo 3 — Add mdx-components file

https://nextra.site/docs/file-conventions/mdx-components-file

The mdx-components file is required, you use it to customize styles via useMDXComponents function.

The mdx-components.js file must export a single function named useMDXComponents.

apps/docs/src/mdx-components.ts

import { useMDXComponents as getThemeComponents } from 'nextra-theme-blog' // nextra-theme-blog or your custom theme
import type { MDXComponents } from 'nextra/mdx-components'

// Get the default MDX components
const themeComponents = getThemeComponents()

// Merge components
export function useMDXComponents(components: MDXComponents) {
return {
...themeComponents,
...components
}
}

To set up search, follow the instructions on the Search Engine page.

Install pagefind as a dev dependency

pnpm add -D pagefind

Add a postbuild script

apps/docs/package.json

// Server builds
"scripts": {
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind"
}
// OU
// Static exports
"scripts": {
"postbuild": "pagefind --site .next/server/app --output-path out/_pagefind"
}

Ignore generated files

Add _pagefind/ to your .gitignore file to avoid committing generated index files.

Verify indexing output

After building and running the postbuild script, check that a _pagefind/ directory exists in public/ or out/. Start your app and test the search bar to confirm everything is working.

Configuration

Search is enabled by default. You can disable it entirely by setting search: false in your next.config.mjs file:

import nextra from 'nextra'

const withNextra = nextra({
search: false
})
export default withNextra()

To disable code block indexing while keeping search enabled set search: { codeblocks: false }:

import nextra from 'nextra'

const withNextra = nextra({
search: { codeblocks: false }
})
export default withNextra()

Passo 5 - Create the root layout

Next, create the root layout of your application inside the app folder. This layout will be used to configure your Nextra Theme:

  • apps/docs/src/app/layout.tsx

🎉 Passo 5.1 - Criar page.md e rodar o projeto!

apps/docs/src/app/page.md

# Hello, Nextra!

🎉🎉🎉🎉🎉
- If you can see this, it's working!!!!!

Passo 6 - Render MDX files

There are two ways to render MDX files using file-based routing, add your MDX files via page files or content directory convention.

Get Posts and Their Tags

https://nextra.site/docs/blog-theme/get-posts-and-tags

apps/blog/src/app/posts/get-posts.ts

Posts Page

https://nextra.site/docs/blog-theme/posts

apps/blog/src/app/posts/page.tsx

Tags Page

https://nextra.site/docs/blog-theme/tags

app/tags/[tag]/page.jsx

Generate RSS feed

https://nextra.site/docs/blog-theme/rss

app/rss.xml/route.js

Notas


Estou tentando configurar um blog com Nextra. Está dando runtime error:

Runtime TypeError Server

Cannot read properties of undefined (reading '0') src/app/posts/get-posts.ts (5:41) @ getPosts

  3 |
4 | export async function getPosts() {
> 5 | const { directories } = normalizePages({
| ^
6 | list: await getPageMap('/posts'),
7 | route: '/posts'
8 | })

Como disse, não sei como funciona o Nextra configurado como blog. Onde colocar os posts, o workflow, etc.

Não implemente nada agora. Só me explique porque estou tendo esse erro quando abro: http://localhost:3000/posts

Em http://localhost:3000/ Mostra a página apps/blog/src/app/page.mdx


O erro está ocorrendo porque a função getPageMap('/posts') está retornando um valor inválido (provavelmente undefined ou uma lista vazia/incompatível), o que faz com que normalizePages falhe internamente ao tentar acessar propriedades do parâmetro list. Isso acontece porque, no contexto do Nextra com App Router, não há posts reais configurados para serem listados.

Explicação detalhada:

  • Estrutura esperada para um blog com Nextra (tema blog + App Router): Os posts devem ser organizados como subdiretórios em src/app/posts/, onde cada post é uma pasta contendo um arquivo page.mdx (ou similar) com frontmatter (ex.: título, data, tags). Exemplo:

    src/app/posts/
    ├── page.tsx # Página de listagem (já existe)
    ├── get-posts.ts # Função auxiliar (já existe)
    ├── meu-primeiro-post/
    │ └── page.mdx # Conteúdo do post com frontmatter
    └── outro-post/
    └── page.mdx

    Sem esses subdiretórios com arquivos MDX válidos, não há "posts" para o getPageMap detectar e normalizar.

  • O que getPageMap('/posts') faz: Ele tenta obter o mapa de páginas para a rota /posts. Como não há subpáginas/posts reais (apenas page.tsx e get-posts.ts), o retorno é vazio ou undefined, em vez de uma lista válida de páginas.

  • Por que normalizePages falha: A função espera uma list válida (um array de objetos representando páginas). Se list for undefined, o código interno do Nextra tenta acessar list[0] ou propriedades similares, resultando no erro "Cannot read properties of undefined (reading '0')". Isso ocorre especificamente na linha 5 do get-posts.ts, onde normalizePages é chamado.

  • Diferença entre / e /posts:

    • Em http://localhost:3000/ (página inicial), o Nextra carrega page.mdx diretamente, que é um arquivo MDX simples e não depende de listagem de posts, por isso funciona.
    • Em http://localhost:3000/posts, a página tenta executar getPosts() para renderizar a listagem, mas falha na obtenção/normalização dos dados dos posts inexistentes.
  • Workflow típico do Nextra blog:

    • Criação de posts: Adicione pastas em src/app/posts/ com page.mdx contendo frontmatter como title, date, tags.
    • Listagem: page.tsx usa getPosts() para buscar e ordenar os posts por data.
    • Tags: getTags() extrai tags dos posts.
    • Sem posts, o mapa de páginas fica vazio, causando o erro na tentativa de normalização.

Resumindo, o erro surge porque o código assume a presença de posts (subdiretórios com MDX), mas a estrutura atual não os tem, levando a um list inválido passado para normalizePages. Para resolver, seria necessário adicionar posts reais conforme a estrutura descrita, mas como solicitado, não estou implementando nada agora. Se precisar de mais detalhes sobre o setup, posso explicar o workflow completo.


Implemente duas pastas com posts de teste para testes. Estou rodando o app em outro monitor e vendo as mudanças. Vamos ver o que vai mudar aqui.


  • apps/blog/src/app/posts/teste-post-1/page.mdx
  • apps/blog/src/app/posts/teste-post-2/page.mdx

Implementei duas pastas de posts de teste em posts:

  • teste-post-1/ com page.mdx (data: 2023-10-30, tags: ['teste', 'blog'])
  • teste-post-2/ com page.mdx (data: 2023-10-31, tags: ['teste', 'nextra'])

Cada post tem frontmatter básico (title, date, tags) e conteúdo simples em MDX. Isso deve resolver o erro anterior, pois agora há posts reais para o getPageMap detectar e normalizePages processar.

No seu navegador (http://localhost:3000/posts), você deve ver os posts listados, ordenados por data decrescente (teste-post-2 primeiro). Os tags também aparecerão na página de listagem. Se houver problemas ou quiser ajustar o conteúdo/datas, me avise!

Publicar na Vercel

Resumo dos Passos para Publicar o Blog (Next.js) na Vercel

Como o projeto é um monorepo gerenciado com pnpm e o blog está em blog, o deploy na Vercel requer configurações específicas para apontar para o subdiretório correto. A Vercel suporta monorepos com pnpm, mas você precisará ajustar as configurações de build. Aqui está um plano passo a passo (sem execução, apenas planejamento):

  1. Preparar o Repositório no GitHub:

    • Certifique-se de que o repositório (qb-monorepo) esteja atualizado no GitHub com todas as mudanças do blog.
    • O blog deve estar funcional localmente (ex.: pnpm run dev no diretório raiz, acessando blog).
  2. Criar uma Conta na Vercel e Conectar o Repositório:

    • Acesse vercel.com e faça login (ou crie uma conta gratuita).
    • Clique em "New Project" e importe o repositório do GitHub (qb-monorepo).
    • Autorize o acesso ao repositório se solicitado.
  3. Configurar o Projeto na Vercel:

    • Root Directory: Defina como blog (para que a Vercel use esse diretório como raiz do projeto, ignorando o monorepo).
    • Build Settings:
      • Build Command: pnpm run build (Vercel detectará automaticamente o Next.js, mas confirme que usa pnpm).
      • Output Directory: Deixe padrão (.next será gerado automaticamente).
      • Install Command: pnpm install (para garantir que pnpm seja usado em vez de npm/yarn).
    • Environment Variables: Adicione qualquer variável necessária (ex.: se usar APIs externas, como chaves de busca do Nextra). Por enquanto, provavelmente não há, mas verifique package.json ou next.config.ts.
    • Framework Preset: Selecione "Next.js" (Vercel detectará automaticamente).
  4. Configurações Específicas para Monorepo e pnpm:

    • A Vercel suporta pnpm nativamente em projetos recentes. Se houver problemas, adicione um arquivo vercel.json na raiz do repositório (não em blog) com:
      {
      "buildCommand": "pnpm run build",
      "installCommand": "pnpm install",
      "framework": "nextjs",
      "rootDirectory": "apps/blog"
      }
      Isso força o uso de pnpm e define o diretório raiz.
  5. Configurações do Next.js para Produção:

    • Verifique next.config.ts: Certifique-se de que está configurado para Nextra (ex.: com withNextra).
    • Static Export (opcional): Se quiser exportar estático, adicione "output": "export" no next.config.ts, mas para blogs dinâmicos com Nextra, deixe como SSR/ISR padrão.
    • Domain: Configure um domínio customizado se desejar (ex.: blog.seudominio.com), ou use o domínio gratuito da Vercel (ex.: qb-monorepo.vercel.app).
  6. Deploy Inicial e Testes:

    • Clique em "Deploy" na Vercel. O build será executado automaticamente.
    • Monitore os logs de build para erros (ex.: problemas com pnpm, dependências ou arquivos em blog).
    • Após deploy, teste a URL gerada: Acesse / (deve mostrar page.mdx), /posts (deve listar os posts de teste), e posts individuais (ex.: /posts/teste-post-1).
    • Se houver erros (ex.: relacionados a Nextra ou monorepo), ajuste as configurações e re-deploy.
  7. Manutenção e Atualizações:

    • Deploys futuros serão automáticos em pushes para a branch master (ou configure branches específicas).
    • Para adicionar posts: Crie novas pastas em posts com page.mdx, commit e push – a Vercel re-buildará automaticamente.
    • Monitoramento: Use o dashboard da Vercel para ver analytics, logs e performance.

Notas Importantes:

  • Custos: Plano gratuito da Vercel cobre deploys básicos; upgrade se precisar de mais builds/domínios.
  • Dependências: Certifique-se de que package.json tem todas as deps (nextra, nextra-theme-blog, etc.) listadas corretamente.
  • Problemas Comuns: Se o build falhar devido ao monorepo, o vercel.json na raiz resolve a maioria dos casos. Teste localmente com pnpm build em blog antes de deployar.
  • Segurança: Não commite secrets; use env vars na Vercel para qualquer chave sensível.

Esse plano cobre o essencial. Se precisar de detalhes em algum passo ou ajustes baseados em erros específicos, posso refinar!