Ícones no Next.js
O glyphfox funciona nos dois roteadores. No App Router, os componentes React precisam de uma fronteira de cliente feita com um único arquivo, e os Server Components podem gerar os mesmos ícones como SVG puro, sem enviar nenhum JavaScript.
Instalação
Instale o pacote. Ele vem compilado em ESM e CommonJS, com declarações de tipo, então não há nada para adicionar ao next.config.js: sem transpilePackages, sem loader e sem folha de estilo.
npm install glyphfox
Os componentes React vêm de glyphfox/react e são explicados em detalhes no guia do React. Esta página trata do que muda no Next.js: onde os componentes podem rodar, como manter os ícones fora do bundle do cliente e o que o servidor renderiza.
Por que o App Router precisa de uma fronteira de cliente
No App Router, todo módulo é um Server Component, a menos que o arquivo seja marcado com "use client". Os componentes do glyphfox guardam o estado de hover, de clique e de morph com useState, useEffect e useRef, e o pacote não traz a diretiva "use client". Por isso, renderizar <HomeIcon /> direto do app/page.tsx falha com um erro sobre hooks que só funcionam em Client Components.
A solução não é transformar páginas inteiras em Client Components. Marque um arquivo pequeno como fronteira e importe os seus ícones por meio dele. Server Components podem renderizar Client Components; os ícones continuam sendo renderizados em HTML no servidor e depois hidratados no navegador.
Um arquivo de cliente para os seus ícones
'use client'; export { HomeIcon, SearchIcon, BellIcon, MenuIcon, Icon, MorphIcon, Fusion } from 'glyphfox/react';
import { HomeIcon, SearchIcon, Fusion } from '@/components/icons'; export default function Page() { return ( <header className="bar"> <a href="/"><HomeIcon size={20} /> Home</a> <SearchIcon title="Search" /> <Fusion icons={['cloud', 'lock']} layout="overlap" /> </header> ); }
Reexporte os nomes um por um. O Next.js não aceita export * em uma fronteira de cliente, e as exportações nomeadas preservam o tree shaking: só os ícones listados no arquivo, e só os que uma página realmente usa, vão parar nos chunks do cliente.
Dentro de um arquivo que já começa com "use client", você pode importar direto de glyphfox/react. O arquivo de fronteira só existe para os ícones que você renderiza a partir de arquivos de servidor.
Ícones interativos em Client Components
Manipuladores de eventos e refs não podem passar de um Server Component para um Client Component, porque as props nessa fronteira precisam ser serializáveis. Tudo o que reage ao usuário, como um toggle com morph, deve ficar no seu próprio Client Component:
'use client'; import { useState } from 'react'; import { MorphIcon } from 'glyphfox/react'; export function ThemeToggle() { const [dark, setDark] = useState(true); return ( <button type="button" onClick={() => setDark((d) => !d)} aria-label={dark ? 'Switch to the light theme' : 'Switch to the dark theme'} > <MorphIcon name={dark ? 'moon' : 'sun'} duration={300} /> </button> ); }
O servidor renderiza o ícone inicial, e o morph acontece no navegador assim que o componente é hidratado. Mantenha o primeiro estado idêntico nos dois lados: ler o localStorage ou a hora durante a renderização faz o servidor e o cliente divergirem, e o React acusa uma incompatibilidade de hidratação.
Ícones sem JavaScript em Server Components
A maioria dos ícones nunca muda depois que a página carrega. Para esses, um Server Component pode transformar os dados do ícone em uma string SVG com renderToString de glyphfox/core. O código roda só no servidor, então os ícones já chegam como marcação e não acrescentam nada ao bundle do cliente.
import { renderToString } from 'glyphfox/core'; import type { IconData, SvgOptions } from 'glyphfox'; type Props = SvgOptions & { icon: IconData }; export function ServerIcon({ icon, ...options }: Props) { return <span className="icon" dangerouslySetInnerHTML={{ __html: renderToString(icon, options) }} />; }
import fileText from 'glyphfox/icons/file-text'; import externalLink from 'glyphfox/icons/external-link'; import { ServerIcon } from '@/components/server-icon'; export default function Docs() { return ( <a href="https://example.com/handbook"> <ServerIcon icon={fileText} size={18} /> Handbook <ServerIcon icon={externalLink} size={14} title="opens in a new tab" /> </a> ); }
Cada import de glyphfox/icons/NAME é um pequeno módulo de dados. Quando você quiser usar nomes e a sintaxe de nomes completa (file+check, cloud&lock:side, heart/solid), use em vez disso o toSvg do ponto de entrada principal. Ele traz o registro inteiro de ícones, mas em um Server Component esse peso fica só no servidor:
import { toSvg } from 'glyphfox'; export default function Status() { return <span className="icon" dangerouslySetInnerHTML={{ __html: toSvg('file+check', { size: 20 }) }} />; }
As opções usam os nomes do core: class em vez de className, e style como string. O dangerouslySetInnerHTML precisa de um elemento wrapper; faça com que ele se comporte como o próprio ícone, para não acrescentar altura de linha:
.icon { display: inline-flex; line-height: 0; vertical-align: middle; }
Os estados vivos continuam funcionando sem JavaScript, porque a animação é uma pequena folha de estilo dentro do SVG: renderToString(loader, { animate: 'spin' }) gira, e renderToString(menu, { morphTo: x, trigger: 'hover' }) se transforma no hover com uma transição CSS. Os gatilhos de clique e de estado precisam de um Client Component.
Ícones por nome no servidor
Icon, morphTo e Fusion procuram os nomes em um registro que cada componente de ícone importado preenche. Um nome que ninguém importou é buscado com um import dinâmico depois que a página carrega, o que significa que o servidor envia um SVG vazio para ele e o ícone aparece de repente depois da hidratação. Registre no arquivo de fronteira os dados de todo nome que você renderiza a partir de strings; ele roda no servidor durante a renderização e de novo no navegador:
'use client'; import { registerIcons } from 'glyphfox/react'; import { iconSettings, iconUsers, iconChartBar } from 'glyphfox'; registerIcons([iconSettings, iconUsers, iconChartBar]); export { Icon, HomeIcon } from 'glyphfox/react';
As exportações de dados passam pelo tree shaking assim como os componentes. Atenção a uma diferença entre ambientes: builds de desenvolvimento podem avaliar todos os módulos de ícone, então um registro esquecido pode parecer funcionar com next dev e só renderizar vazio depois do next build, em que os módulos não usados são descartados.
Pages Router
O Pages Router não tem Server Components, então os componentes funcionam como estão em páginas, layouts e no _app. Toda página é renderizada no servidor e hidratada, o que torna o registro acima igualmente útil: coloque o registerIcons em pages/_app.tsx se você renderiza ícones por nome.
import type { AppProps } from 'next/app'; import { registerIcons } from 'glyphfox/react'; import { iconSettings, iconUsers } from 'glyphfox'; registerIcons([iconSettings, iconUsers]); export default function App({ Component, pageProps }: AppProps) { return <Component {...pageProps} />; }
Um ícone como favicon
O App Router usa app/icon.svg como ícone do site. A CLI grava qualquer ícone ali com uma cor explícita, já que um favicon não tem uma cor de texto ao redor para o currentColor seguir:
npx glyphfox copy fox --color "#ff7a1a" --weight 2.5 --out app/icon.svg
Para ícones de app em PNG em vários tamanhos, o kit de marca do estúdio exporta um conjunto de favicons personalizado com a sua cor, o seu peso e o seu estilo.
O que a saída do build contém
Com webpack, o next build gera muitos chunks de cliente minúsculos, mais ou menos um por ícone do conjunto. Eles vêm do carregador sob demanda por trás de Icon e morphTo: o import dinâmico dele aponta para a pasta de ícones, então o bundler prepara um chunk para cada ícone que poderia ser pedido pelo nome. Cada um tem algumas centenas de bytes e só é baixado quando uma página renderiza um nome que não foi importado. Seja qual for o bundler, registrar os nomes que você renderiza a partir de strings (acima) faz com que eles nunca sejam pedidos. Os ícones que você importa diretamente ficam nos seus chunks normais. As medições estão no guia de tree shaking.
Props
Os componentes recebem as mesmas props que em qualquer app React. Quando um Server Component os renderiza através da fronteira, todas as props abaixo são valores simples e podem atravessá-la; só callbacks como onClick e ref precisam ser definidos dentro de um Client Component. As opções do core em renderToString e toSvg usam os mesmos nomes.
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
size | number, string | 24 | Largura e altura; números são pixels. |
color | string | currentColor | Cor principal. |
secondaryColor | string | color | Cor da camada duotone e do preenchimento sólido. |
secondaryOpacity | number | 0.3 / 1 | Opacidade da camada duotone ou do preenchimento sólido. |
weight | number | 2 | Traço de 1 a 3 na grade de 24, com compensação óptica abaixo de 24 px. |
absoluteWeight | boolean | false | Mantém o traço com weight pixels em qualquer tamanho. |
corners | round, sharp | round | Junções e pontas redondas ou retas. |
variant | outline, duotone, solid | outline | Eixo de estilo; o sólido vem das formas fechadas. |
rotate | number | 0 | Rotação em graus. |
flipX | boolean | false | Espelha na horizontal. |
flipY | boolean | false | Espelha na vertical. |
mirrorRtl | boolean | false | Espelha em layouts da direita para a esquerda. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | nenhum | Animação de estado vivo. |
trigger | mount, hover, click, state | mount | Quando a animação ou o morph acontece. |
active | boolean | false | O estado para o gatilho state. |
duration | number (ms) | por animação | Duração da animação ou do morph. |
delay | number (ms) | 0 | Atraso da animação ou do morph. |
loop | boolean, number | false | Repete para sempre ou um número de vezes (spin e pulse repetem por padrão). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | nenhum | Selo com espaço de recorte automático. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Canto do selo. |
morphTo | name, data | nenhum | Destino do morph; o gatilho decide quando aparece. |
title | string | nenhum | Nome acessível: title, role img e aria-label. |
decorative | boolean | true sem title | Esconde o ícone das tecnologias assistivas. |
Armadilhas comuns
- Erros de hooks em Server Components
- Importar de
glyphfox/reactem um arquivo de servidor falha porque os componentes usam hooks. Importe por meio de um arquivo com"use client"ou renderize SVG estático comrenderToString. export *na fronteira- O Next.js rejeita
export *em uma fronteira de cliente. Liste os ícones com exportações nomeadas. - Funções como props vindas do servidor
onClick,onMouseEntere refs não são serializáveis. Leve os ícones interativos para um Client Component e passe para ele apenas dados.- Ícones vazios depois do
next build - Um nome em
Iconque nunca foi importado renderiza vazio no servidor. Registre os dados dele comregisterIconsno arquivo de fronteira ou no_app. - Nomes desconhecidos lançam erro
- O
toSvglança um erro para um nome que não existe, o que resulta em um erro no servidor. Antes de renderizar nomes fornecidos pelo usuário, confira se eles estão emiconNames, deglyphfox. - Espaço extra em volta dos ícones do servidor
- O wrapper do
dangerouslySetInnerHTMLé um elemento inline com a sua própria caixa de linha. Aplique neledisplay: inline-flexeline-height: 0.
Perguntas frequentes
Preciso de transpilePackages para o glyphfox?
Não. O pacote traz JavaScript puro em ESM e CommonJS, com declarações de tipo, então o Next.js o resolve e o inclui no bundle como qualquer dependência compilada.
Posso usar o web component glyph-fox no Next.js?
Sim, importe glyphfox/web a partir de um Client Component. O módulo pode ser avaliado com segurança durante a renderização no servidor, mas um custom element só é desenhado quando o navegador faz o upgrade dele, então o HTML do servidor tem um elemento vazio onde o ícone vai ficar. Em um app React, os componentes são a melhor opção, porque o SVG deles já está no HTML do servidor; o guia de web components explica o elemento.