Montei um projeto Next.js com duas rotas que fazem exatamente a mesma conta pesada. Uma em TypeScript, do jeito de sempre, e a outra num arquivo route.go, em Go, que o Next.js passou a aceitar como experimental no começo de novembro de 2026. Depois joguei 64 conexões ao mesmo tempo em cada uma.
A rota em TypeScript respondeu umas 33 requisições por segundo, e as mais lentas esperaram quase 8 segundos. A rota em Go ficou na faixa de 280 por segundo, com as mais lentas abaixo de 400 milissegundos.

Só que eu rodei um segundo teste, com uma rota que apenas espera 50 milissegundos, tipo uma consulta no banco. Com as mesmas 64 conexões, o placar foi 1.220 contra 1.216 requisições por segundo. Um empate.
Já tem vídeo dizendo que isso muda tudo. Eu li as duas PRs que trouxeram o route.go pro Next.js e rodei o teste na minha máquina. Aqui vai a versão escrita, com os números e com a pergunta que eu acho mais útil: qual rota do seu app valeria a pena escrever em Go.
Como a novidade chegou
Tudo começou com um post do Tim Neutkens, do time do Next.js, em 30 de outubro de 2026. Ele escreveu que o Next.js está prototipando suporte nativo a outras linguagens, começando por Route Handlers e Proxy em Go, para dar mais alternativas quando o servidor precisa fazer muito processamento. O post passou de 260 mil visualizações.
Em 3 de novembro entraram duas PRs no repositório do Next.js, as duas do Tim, do time do Turbopack, e as duas com [experiment] no título:
- #99980 ensina o Turbopack, o bundler do Next.js, a compilar arquivos
.go. Quem compila de verdade é o próprio Go: o Turbopack chama o compilador, acompanha quais arquivos entraram e compila de novo quando você muda um deles. - #100130 usa isso pra criar o
route.go. É o mesmo Route Handler que você conhece doroute.ts, aquele arquivo que vira um endpoint de API dentro da pastaapp, só que escrito em Go.
Ele só liga com uma flag experimental no next.config, a goRoutes. O arquivo é Go normal: package main e a biblioteca padrão. Você escreve uma função GET ou POST (ou qualquer outro método HTTP) que recebe o ResponseWriter e o Request do net/http, igual a um handler de Go fora do Next. Você não escreve a função main, porque o Next.js gera ela por você.
Como o route.go roda de verdade
O Next.js continua rodando em Node. É o Node que recebe a requisição, faz o roteamento, aplica os rewrites e roda o seu proxy.ts. Quando a rota final é um route.go, ele repassa a requisição pra um executável Go, que roda num processo separado. O próprio Next.js cuida desse processo: sobe, reinicia se ele cair e troca pela versão nova quando você edita o arquivo.
Página, Layout e React continuam no Node. O Go só precisa estar instalado no next dev e no next build. O next start roda o executável já compilado, sem o compilador.
O que ainda falta
Antes de reescrever a sua API, tem umas coisas que mudam a decisão:
- É canary e fica atrás de flag. Só existe no canary do Next.js 16.5, e as duas PRs têm
experimentno nome. Pode mudar de forma ou sumir antes de chegar numa versão estável. - Deploy. Se você roda o Next no seu servidor, com
next buildenext start, funciona. Pra rodar numa plataforma, ela precisa de um adapter que saiba executar uma rota em Go, e o README dos testes do próprio PR diz que o experimento não tem o runtime Go hospedado pronto. - Sem cross-compilation. O build gera o executável para o sistema e a arquitetura da máquina que buildou. Se você builda no Mac e sobe a pasta pra um servidor Linux, a rota em Go não roda. O build tem que acontecer num ambiente igual ao de produção.
- Outras limitações da documentação: não tem WebSocket, as dependências do Go precisam já estar baixadas (o build não baixa nada), e não existe
page.gonemlayout.go. O Go fica só nas rotas de API. - O Proxy em Go do post do Tim não veio. Não existe
proxy.go, e oproxy.tscontinua em Node.
O teste
Montei um projeto Next.js com pares de rotas, uma route.ts e uma route.go, fazendo a mesma coisa, e um script em Node que dispara requisições com 1, 4, 16 e 64 conexões ao mesmo tempo, 5 segundos em cada nível. Rodei tudo no canary do Next.js 16.5, num Mac com chip M5 de 10 núcleos, em duas rodadas que deram resultados iguais dentro de uns 5%.
A primeira rota calcula Fibonacci de 35 do jeito recursivo, que é o jeito mais burro de calcular Fibonacci. Não lê banco nem chama API: é CPU pura do começo ao fim, e as duas versões devolvem o mesmo número. A versão em Go (app/api/cpu-go/route.go do projeto de teste):
package main
import (
"encoding/json"
"net/http"
"strconv"
)
func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}
func GET(w http.ResponseWriter, r *http.Request) {
n, err := strconv.Atoi(r.URL.Query().Get("n"))
if err != nil {
n = 35
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"runtime": "go", "n": n, "result": fib(n)})
}
E a versão em TypeScript (app/api/cpu-node/route.ts):
function fib(n: number): number { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
export async function GET(request: Request) {
const n = Number(new URL(request.url).searchParams.get("n") ?? 35);
return Response.json({ runtime: "node", n, result: fib(n) });
}
Isso é um microbenchmark, com as limitações de um microbenchmark. Ninguém calcula Fibonacci numa rota de verdade, e o gerador de carga rodou na mesma máquina, disputando CPU com o servidor. Os números servem pra ver o comportamento de quem escala e de quem não escala, e não como o que o seu app vai ter em produção.
Rota de CPU pura
| Conexões | Node (req/s) | Go (req/s) |
|---|---|---|
| 1 | 21 | 51 |
| 4 | 21 | 175 |
| 64 | ~33 | ~280 |
Com uma conexão só, o Node levou uns 48 ms por chamada e o Go uns 20. Com 4 conexões o Node continuou em 21 por segundo, e o Go foi pra 175, perto de 8 vezes mais.
O motivo está em como cada um roda o código. O JavaScript da rota roda numa thread só, no event loop: enquanto ele calcula um Fibonacci, não atende mais ninguém, e as outras três requisições ficam na fila. A máquina tem 10 núcleos, com 9 sobrando. O servidor HTTP do Go atende cada requisição numa goroutine, e o runtime espalha as goroutines por todos os núcleos, então as 4 requisições andam ao mesmo tempo.
Com 64 conexões, o Go entregou umas 9 vezes mais requisições. O p95, o tempo abaixo do qual ficam 95% das requisições, é o que mais aparece pra quem espera do outro lado: quase 8 segundos no Node contra menos de 400 milissegundos no Go, umas 20 vezes menos.

Rota que só espera
A segunda rota não faz conta nenhuma. Ela espera 50 milissegundos e responde, que é o que uma rota real faz quando consulta o banco ou chama a API de outro serviço.
| Conexões | Node (req/s) | Go (req/s) |
|---|---|---|
| 1 | 18,6 | 18,8 |
| 16 | 300,8 | 300,8 |
| 64 | 1.220 | 1.216 |
Com 64 conexões o p95 ficou em uns 56 ms nas duas. Faz sentido, porque esperar é justamente o que o event loop do Node faz bem: enquanto uma requisição espera, ele atende a próxima. A thread só fica presa quando tem conta pra fazer. E se você parar pra olhar as rotas do seu app, boa parte passa quase o tempo todo esperando o banco. Pra essas, reescrever em Go não muda nada.
Medi também uma rota que só devolve um JSON pequeno, pra ver se o salto do Node pro processo do Go custava caro. Com uma conexão, a mediana ficou em 0,1 ms nas duas, então o salto não apareceu na medição.
Para ser justo com o Node
Eu fui injusto com ele na rota de CPU, sim. Peguei o pior caso do Node: conta pesada numa thread só, num processo só. O Node tem worker threads, que servem justamente pra rodar JavaScript em paralelo, e dá pra subir mais de um processo, um por núcleo. Eu não medi nenhuma dessas opções, então não tenho número delas.
O que eu posso dizer é a diferença de esforço. No Node você precisa montar esse paralelismo. No Go você escreve a função e o runtime já usa os núcleos sozinho.
Meu veredito
Isso não muda tudo. A pergunta deixa de ser "meu back-end vai ser em Node ou em Go", que é uma decisão pro projeto inteiro, e vira "o que essa rota aqui faz?".
Se ela passa o tempo esperando o banco, fica em TypeScript, junto com o resto do app, e o Go não te dá nada. Se ela faz conta pesada, tipo processar uma imagem, comprimir um arquivo ou ler um CSV gigante, uma rota em Go faz sentido: no mesmo repositório, no mesmo build, sem subir um serviço separado. O Next.js fica no meio, decidindo quem atende cada requisição.
Eu não colocaria isso em produção enquanto for canary e estiver atrás de flag. Costumo esperar uma tecnologia sobreviver ao teste do tempo antes de adotar, e se isso chegar numa versão estável, com o deploy resolvido nas plataformas, eu testo de novo.
Resumindo, o route.go funciona. Ele roda num processo Go separado com o Next.js na frente, ganhou de 8 a 9 vezes numa rota de CPU pura e empatou numa rota que só fica esperando. Serve pra uma rota específica do seu app, e não pra reescrever a API inteira.
Vídeo no YouTube: Testei Go dentro do Next.js
Conheça meu trabalho: https://bergholz.com.br/
Obrigado por ler até aqui.
