Frameworks

Í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.

terminal
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

components/icons.ts
'use client';

export { HomeIcon, SearchIcon, BellIcon, MenuIcon, Icon, MorphIcon, Fusion } from 'glyphfox/react';
app/page.tsx
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:

components/theme-toggle.tsx
'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.

components/server-icon.tsx
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) }} />;
}
app/docs/page.tsx
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:

app/status/page.tsx
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:

app/globals.css
.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:

components/icons.ts
'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.

pages/_app.tsx
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:

terminal
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.

PropTipoPadrãoDescrição
sizenumber, string24Largura e altura; números são pixels.
colorstringcurrentColorCor principal.
secondaryColorstringcolorCor da camada duotone e do preenchimento sólido.
secondaryOpacitynumber0.3 / 1Opacidade da camada duotone ou do preenchimento sólido.
weightnumber2Traço de 1 a 3 na grade de 24, com compensação óptica abaixo de 24 px.
absoluteWeightbooleanfalseMantém o traço com weight pixels em qualquer tamanho.
cornersround, sharproundJunções e pontas redondas ou retas.
variantoutline, duotone, solidoutlineEixo de estilo; o sólido vem das formas fechadas.
rotatenumber0Rotação em graus.
flipXbooleanfalseEspelha na horizontal.
flipYbooleanfalseEspelha na vertical.
mirrorRtlbooleanfalseEspelha em layouts da direita para a esquerda.
animatedraw, pop, wiggle, spin, pulse, bounce, shakenenhumAnimação de estado vivo.
triggermount, hover, click, statemountQuando a animação ou o morph acontece.
activebooleanfalseO estado para o gatilho state.
durationnumber (ms)por animaçãoDuração da animação ou do morph.
delaynumber (ms)0Atraso da animação ou do morph.
loopboolean, numberfalseRepete para sempre ou um número de vezes (spin e pulse repetem por padrão).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotnenhumSelo com espaço de recorte automático.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCanto do selo.
morphToname, datanenhumDestino do morph; o gatilho decide quando aparece.
titlestringnenhumNome acessível: title, role img e aria-label.
decorativebooleantrue sem titleEsconde o ícone das tecnologias assistivas.

Armadilhas comuns

Erros de hooks em Server Components
Importar de glyphfox/react em um arquivo de servidor falha porque os componentes usam hooks. Importe por meio de um arquivo com "use client" ou renderize SVG estático com renderToString.
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, onMouseEnter e 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 Icon que nunca foi importado renderiza vazio no servidor. Registre os dados dele com registerIcons no arquivo de fronteira ou no _app.
Nomes desconhecidos lançam erro
O toSvg lanç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 em iconNames, de glyphfox.
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 nele display: inline-flex e line-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.

Ícones deste guia