npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@userkit/nextjs

v0.7.0

Published

UserKit for the Next.js App Router: route handlers that keep the session in an httpOnly cookie, server-side session reads, and a route guard.

Downloads

1,422

Readme

@userkit/nextjs

UserKit para o App Router do Next.js.

npm install @userkit/nextjs

Versão 0.2.3, acompanhando o @userkit/js e o @userkit/react. Os três pacotes são versionados juntos.

O que este pacote muda

O @userkit/react sozinho fala com a API direto do navegador e guarda a sessão no localStorage. É o arranjo para o qual a superfície de plano de clientes foi desenhada, e ele não precisa de servidor nenhum.

Só que um app Next tem servidor. Então aqui a sessão vira um cookie httpOnly: script na página não consegue lê-la, a chave publicável não entra no bundle, e — o que o arranjo do navegador não consegue dar — a página sabe quem é a pessoa antes de renderizar.

É o mesmo arranjo que o painel do próprio UserKit usa.

Fora isso, é o mesmo pacote: todo componente e todo hook do @userkit/react sai daqui também, pelo mesmo nome, apontado para os handlers em vez da API. Instale e importe de um lugar só. A lista de reexports é explícita e não um export *, porque cada nome nela precisa de uma rota encaminhada do outro lado — o useFeatureWaitlist é hoje o único que não está aqui, e é por isso.

Três coisas para montar

// app/api/userkit/[...userkit]/route.ts
import { createUserKitHandlers } from "@userkit/nextjs/handlers";

export const { GET, POST, PUT, DELETE } = createUserKitHandlers();
// app/layout.tsx
import { UserKitProvider } from "@userkit/nextjs";

export default function Layout({ children }) {
  return <UserKitProvider>{children}</UserKitProvider>;
}
// proxy.ts   (middleware.ts no Next 15)
import { createUserKitGuard } from "@userkit/nextjs/proxy";

export const proxy = createUserKitGuard({ signInPath: "/sign-in" });
export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"] };

E uma variável, que fica no servidor:

USERKIT_PUBLISHABLE_KEY=uk_pk_live_…

Repare que ela não é NEXT_PUBLIC_. A chave é publicável — no arranjo direto ela mora no HTML — mas aqui ela não tem motivo para entrar no bundle, e uma variável que não pode vazar é melhor que uma que só por acaso não vaza.

No servidor

// app/dashboard/page.tsx
import { getSession } from "@userkit/nextjs/server";

export default async function Dashboard() {
  const session = await getSession();
  return <h1>Olá, {session?.contact.name}</h1>;
}

getSession() é memoizado por requisição com o cache do React: chamar num layout e mais três vezes em componentes custa uma ida só. Entre requisições ele não é cacheado — uma sessão revogada precisa morrer na hora, e isso vale mais que o hop.

getToken() devolve o JWT curto para mandar a outro serviço — sua própria API, um worker, qualquer coisa que valide contra o JWKS em vez de nos chamar. Dentro deste app você não precisa dele: o getSession() já sabe.

null quer dizer deslogado, e só isso

Vale para as três leituras — getSession(), getContact() e getToken(). Quando a API não responde — rede morta, 500, 503 — elas lançam um UserKitError em vez de devolver null: são dois fatos diferentes, e juntá-los renderiza uma página deslogada para quem está logado. No getToken() o estrago aparece um hop adiante e mais quieto: um token nulo vira uma requisição sem Authorization, e o serviço lá na frente responde 401 — reportando problema de autenticação para o que foi uma oscilação entre este app e nós.

O requireSession() distingue os dois casos pelo status do erro (401 é "não tem ninguém"; qualquer outro é "não deu para saber"). Se numa tela específica uma leitura degradada for melhor que um erro, capture — mas essa decisão é sua, e é por isso que ela não vem tomada.

Do outro lado: validando o JWT

// app/api/coisa/route.ts, ou qualquer serviço que não seja o app Next
import { verifyContactToken } from "@userkit/nextjs/verify";

export async function GET(request: Request) {
  const token = request.headers.get("authorization")?.replace(/^Bearer /, "");
  const claims = token ? await verifyContactToken(token) : null;
  if (!claims) return new Response("unauthorized", { status: 401 });

  return Response.json({ contact: claims.contactId });
}

Assinatura e revogação, as duas offline. O conjunto de chaves e a lista de sessões revogadas são buscados de tempos em tempos e guardados em cache no processo — nenhuma chamada nossa no caminho da requisição, que é a propriedade que o arranjo todo existe para comprar.

A lista de revogação é o que faz "revogar" significar agora em vez de dentro de cinco minutos: um JWT não pode ser chamado de volta, então uma sessão revogada continuaria valendo até o exp dela. claims.revocationCheck diz qual dos três casos você pegou — fresh, stale (a última cópia, ainda dentro da janela dela) ou unavailable.

unavailable aceita o token, de propósito: se não conseguimos responder, a degradação certa é voltar ao limite que sempre existiu — os poucos minutos do próprio token — e nunca para "ninguém está logado". Passe requireRevocationCheck: true nas poucas ações em que essa troca é outra.

Este módulo não importa nada de next: o serviço que confere um token muitas vezes não é o app que o emitiu, e essa metade roda em qualquer runtime com WebCrypto.

Sobre a guarda de rotas

Ela confere só que o cookie existe. Se aquele token ainda vale, e o que ele pode fazer, é decidido onde os dados estão — no getSession() da página e na API a cada chamada. Manter a checagem barata assim significa nenhuma ida de rede na frente de cada navegação, e uma sessão revogada ainda morre no instante em que a página tenta carregar qualquer coisa.

Leia isso como camadas, não como atalho: a guarda é o que impede alguém de chegar; nunca é o que impede alguém de ler.

O mesmo vale, uma camada abaixo, para o que os handlers repassam: caminho, método, cabeçalhos e parâmetros de query são declarados um a um na allowlist, nunca relaiados em bloco. A query é a parte que o navegador escreve à vontade, e é por isso que ela é nominal — ?unread=true no feed de notificações chega porque a rota diz que ele chega, e qualquer outro parâmetro é descartado antes de a chamada sair daqui.

O config é você quem exporta, e o nome precisa ser exatamente esse. Com qualquer outro nome ele é ignorado em silêncio, o matcher junto, e a guarda passa a rodar em cada asset — um visitante deslogado recebe o CSS redirecionado para a tela de login.

O JWT não volta para o navegador

getToken() no cliente responde erro em modo proxy, de propósito. O arranjo inteiro existe para nenhuma credencial chegar em script; devolver uma ali desfaria isso. O JWT é emitido no seu servidor, onde o cookie já está.

Se você precisa dele no navegador, o arranjo direto (@userkit/react puro) é o que atende — e continua sendo uma escolha legítima.