import type { Metadata } from "next"; import { type CaseStudyContent, isClientCase, type ServicePageContent, siteName, siteUrl, } from "./site-content"; export const twitterHandle = "@evandrodevbr"; export const SEO_IDS = { website: `${siteUrl}/#website`, person: `${siteUrl}/#person`, profilePage: `${siteUrl}/curriculo#profile-page`, } as const; export function buildWebSiteJsonLd() { return { "@type": "WebSite", "@id": SEO_IDS.website, url: siteUrl, name: siteName, inLanguage: "pt-BR", publisher: { "@id": SEO_IDS.person }, } as const; } export function buildPersonJsonLd() { return { "@type": "Person", "@id": SEO_IDS.person, name: "Evandro Fonseca Junior", url: siteUrl, image: `${siteUrl}/edev.jpg`, // cargo e áreas vêm de public/portfolio-data.json e estão visíveis em /curriculo e na seção de skills jobTitle: "Engenheiro da Computação e Desenvolvedor de Software", knowsAbout: [ "Next.js", "React", "TypeScript", "Node.js", "Python", "IoT", "Computação em nuvem", "Inteligência artificial", ], homeLocation: { "@type": "Place", name: "Garuva, Santa Catarina, Brasil", }, sameAs: ["https://www.linkedin.com/in/evandrodevbr/"], } as const; } export function buildProfilePageJsonLd() { return { "@type": "ProfilePage", "@id": SEO_IDS.profilePage, url: `${siteUrl}/curriculo`, inLanguage: "pt-BR", isPartOf: { "@id": SEO_IDS.website }, mainEntity: { "@id": SEO_IDS.person }, } as const; } export function buildServiceJsonLd(service: ServicePageContent) { const pageUrl = `${siteUrl}/servicos/${service.slug}`; return { "@type": "Service", "@id": `${pageUrl}#service`, url: pageUrl, name: service.navLabel, description: service.metaDescription, provider: { "@id": SEO_IDS.person }, areaServed: [ { "@type": "City", name: "Garuva" }, { "@type": "AdministrativeArea", name: "Norte de Santa Catarina", }, ], } as const; } /** Imagem de compartilhamento do case: a mesma que as redes recebem em `og:image` (rota opengraph-image do case). */ export function getCaseImageUrl(id: string) { return `${siteUrl}/cases/${id}/opengraph-image/${id}`; } /** Trabalho do case como `CreativeWork`: autor ligado à pessoa e, nos projetos de cliente, o cliente em nome de quem foi feito. */ export function buildCaseJsonLd(caseStudy: CaseStudyContent) { const pageUrl = `${siteUrl}/cases/${caseStudy.id}`; return { "@type": "CreativeWork", "@id": `${pageUrl}#creative-work`, name: `${caseStudy.title}: ${caseStudy.subtitle}`, description: caseStudy.metaDescription, url: pageUrl, image: getCaseImageUrl(caseStudy.id), inLanguage: "pt-BR", creator: { "@id": SEO_IDS.person }, ...(isClientCase(caseStudy) ? { sourceOrganization: { "@type": "Organization", name: caseStudy.title, ...(caseStudy.demoUrl ? { url: caseStudy.demoUrl } : {}), }, } : {}), keywords: caseStudy.stack.join(", "), }; } export function buildBreadcrumbList( pageUrl: string, items: readonly (readonly [name: string, url: string])[], ) { return { "@type": "BreadcrumbList", "@id": `${pageUrl}#breadcrumb`, itemListElement: items.map(([name, item], index) => ({ "@type": "ListItem" as const, position: index + 1, name, item, })), } as const; } /** * Canonical, Open Graph e Twitter de uma página. Quem define `openGraph` ou `twitter` numa página substitui o do layout inteiro, * então o cartão precisa vir completo daqui; a imagem vem do `opengraph-image` da própria rota. */ export function buildPageSocialMetadata({ url, title, description, type, }: { url: string; title: string; description: string; type: "website" | "article" | "profile"; }): Pick { return { alternates: { canonical: url }, openGraph: { locale: "pt_BR", title, description, url, siteName, type, }, twitter: { card: "summary_large_image", title, description, creator: twitterHandle, }, }; }