Cases da SEV Moda Íntima e da Lavação 047; case no formato cliente, problema, entrega, mídia e contato, com detalhes técnicos recolhidos; sem GitHub, ressalvas e fontes de commit. /cases separa clientes e Laboratório. Serviços mostram o case de cliente certo e o bloco de investimento e prazo só aparece preenchido. Metadata social própria por página, not-found com noindex, sitemap com /ia.
130 lines
3.1 KiB
TypeScript
130 lines
3.1 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { 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;
|
|
}
|
|
|
|
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<Metadata, "alternates" | "openGraph" | "twitter"> {
|
|
return {
|
|
alternates: { canonical: url },
|
|
openGraph: {
|
|
locale: "pt_BR",
|
|
title,
|
|
description,
|
|
url,
|
|
siteName,
|
|
type,
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title,
|
|
description,
|
|
creator: twitterHandle,
|
|
},
|
|
};
|
|
}
|