Meta tags, Open Graph, JSON-LD, Sitemap e mais
Meta tags, Open Graph, JSON-LD, Sitemap e mais
Veja como este portfolio foi otimizado para mecanismos de busca usando as melhores práticas de SEO — com exemplos para Next.js e React puro (Vite). Cada técnica está rodando em produção.
Infraestrutura completa de SEO com exemplos para Next.js (APIs nativas) e React + Vite (bibliotecas da comunidade).
Title template, description, keywords, authors, robots e canonical URL configurados no layout raiz com herança automática para todas as páginas.
Imagem de 1200x630 gerada dinamicamente via ImageResponse com avatar, nome, skills e branding do site.
Schemas Person, WebSite, ProfilePage e BreadcrumbList com @id para referência cruzada — rich snippets no Google com nome, cargo, skills e navegação.
Gerado automaticamente a partir das rotas registradas em content.ts. Novas páginas entram no sitemap sem configuração manual.
Configuração de crawling permitindo indexação total e apontando para o sitemap.xml.
Web App Manifest com nome, cores do tema, ícones e configuração standalone para instalação no mobile.
O layout raiz define metadata global com title template. Páginas filhas herdam e podem sobrescrever via buildPageMetadata().
export const metadata: Metadata = {metadataBase: new URL(SITE_URL),title: {default: `${SITE_NAME} | ${PERSONAL.role}`,template: `%s | ${SITE_NAME}`,},description: `Portfolio de ${PERSONAL.fullName}...`,keywords: ["portfolio frontend", "React", "Next.js","TypeScript", "React Native", "Tailwind CSS",],authors: [{ name: SITE_AUTHOR, url: SITE_URL }],robots: { index: true, follow: true },alternates: { canonical: SITE_URL },openGraph: {title: `${SITE_NAME} — ${PERSONAL.role}`,url: SITE_URL,siteName: SITE_NAME,type: "website",locale: "pt_BR",alternateLocale: ["en_US", "es_ES", "de_DE"],},twitter: { card: "summary_large_image" },verification: { google: PERSONAL.googleVerification },};
export function buildPageMetadata(page: {title: string;description: string;path?: string;}): Metadata {const url = `${SITE_URL}${page.path ?? ""}`;return {title: page.title,description: page.description,alternates: { canonical: url },openGraph: {title: page.title,description: page.description,url,siteName: SITE_NAME,type: "website",locale: "pt_BR",},twitter: {card: "summary_large_image",title: page.title,description: page.description,},};}
Imagem gerada em tempo de build via next/og ImageResponse. Aparece quando o link é compartilhado no LinkedIn, Twitter, WhatsApp ou Slack.
import { ImageResponse } from "next/og";import { readFile } from "node:fs/promises";export const size = { width: 1200, height: 630 };export const contentType = "image/png";export default async function OGImage() {const avatar = await readFile(join(process.cwd(), "public", "avatar-desk.png"),);const src = `data:image/png;base64,${avatar.toString("base64")}`;return new ImageResponse(<div style={{ /* dark bg, avatar, name, skills */ }}><img src={src} width={280} height={280} /><h1>Vinicius Bastazin</h1><p>Desenvolvedor Frontend Senior</p>{skills.map(s => <span>{s}</span>)}</div>,{ ...size },);}
Schema.org com @graph contendo Person, WebSite, ProfilePage e BreadcrumbList — todos linkados via @id. Isso permite que o Google entenda o autor, navegação do site e exiba rich snippets.
const personSchema = {"@type": "Person","@id": `${SITE_URL}/#person`,name: SITE_AUTHOR,url: SITE_URL,jobTitle: PERSONAL.role,sameAs: [PERSONAL.github, PERSONAL.linkedin],knowsAbout: ["React", "Next.js", "TypeScript","React Native", "Tailwind CSS", "Node.js",],image: `${SITE_URL}${PERSONAL.avatar}`,address: {"@type": "PostalAddress",addressLocality: "Presidente Prudente",addressRegion: "SP",addressCountry: "BR",},};const websiteSchema = {"@type": "WebSite","@id": `${SITE_URL}/#website`,name: SITE_NAME,url: SITE_URL,author: { "@id": `${SITE_URL}/#person` },publisher: { "@id": `${SITE_URL}/#person` },inLanguage: ["pt-BR", "en", "es", "de"],potentialAction: {"@type": "SearchAction",target: `${SITE_URL}/?q={search_term_string}`,},};const profilePageSchema = {"@type": "ProfilePage","@id": `${SITE_URL}/#profilepage`,url: SITE_URL,mainEntity: { "@id": `${SITE_URL}/#person` },isPartOf: { "@id": `${SITE_URL}/#website` },};const breadcrumbSchema = {"@type": "BreadcrumbList",itemListElement: [{ "@type": "ListItem", position: 1,name: "Home", item: SITE_URL },{ "@type": "ListItem", position: 2,name: "Implementações",item: `${SITE_URL}/implementacoes` },{ "@type": "ListItem", position: 3,name: "Dicas & Guias",item: `${SITE_URL}/dicas` },],};// Injetado no <head> via layout.tsx<scripttype="application/ld+json"dangerouslySetInnerHTML={{__html: JSON.stringify({"@context": "https://schema.org","@graph": [personSchema, websiteSchema,profilePageSchema, breadcrumbSchema,],}),}}/>
O sitemap é gerado dinamicamente a partir do array CONTENT_ITEMS. Quando uma nova página é adicionada ao content.ts, ela automaticamente aparece no sitemap.xml.
import { CONTENT_ITEMS } from "@/data/content";import { SITE_URL } from "@/lib/seo";export default function sitemap(): MetadataRoute.Sitemap {const staticPages = [{ url: SITE_URL, priority: 1 },{ url: `${SITE_URL}/implementacoes`, priority: 0.8 },{ url: `${SITE_URL}/dicas`, priority: 0.8 },];const dynamicPages = CONTENT_ITEMS.map((item) => ({url: `${SITE_URL}/${prefix}/${item.slug}`,priority: 0.7,}));return [...staticPages, ...dynamicPages];}
export default function robots(): MetadataRoute.Robots {return {rules: { userAgent: "*", allow: "/" },sitemap: `${SITE_URL}/sitemap.xml`,};}
Métricas de performance que o Google usa como fator de ranking. Monitoradas neste portfolio via Vercel Speed Insights.
Tempo até o maior elemento visível carregar. Ideal: < 2.5s.
Dica: Otimize imagens com next/image, use fontes com display: swap e priorize o conteúdo above-the-fold.
Tempo de resposta a interações do usuário. Ideal: < 200ms.
Dica: Evite tarefas longas no main thread, use React.memo, useCallback e code splitting com dynamic imports.
Quanto o layout se move inesperadamente. Ideal: < 0.1.
Dica: Defina width/height em imagens, use font-display: swap e reserve espaço para conteúdo dinâmico.
Tempo até o primeiro conteúdo aparecer na tela. Ideal: < 1.8s.
Dica: Use SSR/SSG, minimize CSS crítico, evite bloqueio de renderização com scripts e inline critical CSS.
Tempo até o servidor responder. Ideal: < 800ms.
Dica: Use CDN (Vercel Edge), cache de páginas estáticas, otimize queries do servidor e use ISR quando possível.
Dica: use Vercel Speed Insights ou Google PageSpeed Insights para monitorar em produção.
SEO funciona diferente em apps SSR/SSG (Next.js) e SPAs (React + Vite). Veja como resolver cada item nos dois cenários.
Metadata API nativa — export const metadata no layout/page
export const metadata: Metadata = {title: 'Meu Site',description: '...',};
react-helmet-async — <Helmet> em cada rota
import { Helmet } from 'react-helmet-async';<Helmet><title>Meu Site</title><meta name="description" content="..." /></Helmet>
Status de cada item de SEO implementado neste portfolio.
Todo o código de SEO deste portfolio é open source. Clone, adapte e use.