feat(cases): mídia no topo, títulos de busca curtos e CreativeWork
Case com mídia ao lado do título e play acessível; problema de cada cliente reescrito com base nos dados; serviços relacionados em 2 cards; FAQ antes do próximo passo e bloco 'Onde já apliquei isso'; Apolo citada em IoT e arquitetura. metaTitle até 43 caracteres e descrições até 155 com teste; JSON-LD CreativeWork; imagens de compartilhamento abaixo de 100 KB; 404 e robots corrigidos; server-only nos módulos com node:.
This commit is contained in:
1 parent
c9a0b80656
commit
e80f73d075
52 files changed
+2593
-439
No files matched your search
+66
-1
@@ -1,15 +1,23 @@
|
||||
import sitemap from "../app/sitemap";
|
||||
import {
|
||||
buildBreadcrumbList,
|
||||
buildCaseJsonLd,
|
||||
buildPageSocialMetadata,
|
||||
buildPersonJsonLd,
|
||||
buildProfilePageJsonLd,
|
||||
buildServiceJsonLd,
|
||||
buildWebSiteJsonLd,
|
||||
getCaseImageUrl,
|
||||
SEO_IDS,
|
||||
twitterHandle,
|
||||
} from "./seo";
|
||||
import { caseStudies, servicePages, siteName, siteUrl } from "./site-content";
|
||||
import {
|
||||
caseStudies,
|
||||
isClientCase,
|
||||
servicePages,
|
||||
siteName,
|
||||
siteUrl,
|
||||
} from "./site-content";
|
||||
|
||||
const representativeService = servicePages[0];
|
||||
if (!representativeService) throw new Error("Missing representative service");
|
||||
@@ -89,6 +97,63 @@ it("builds a canonical service with only the verified service area", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function getCase(id: string) {
|
||||
const caseStudy = caseStudies.find((item) => item.id === id);
|
||||
if (!caseStudy) throw new Error(`Missing case ${id}`);
|
||||
return caseStudy;
|
||||
}
|
||||
|
||||
it("describes a client case as a CreativeWork made by the person for the client", () => {
|
||||
const caseStudy = getCase("sev-modaintima");
|
||||
const pageUrl = `${siteUrl}/cases/${caseStudy.id}`;
|
||||
|
||||
expect(buildCaseJsonLd(caseStudy)).toEqual({
|
||||
"@type": "CreativeWork",
|
||||
"@id": `${pageUrl}#creative-work`,
|
||||
name: "SEV Moda Íntima: Catálogo digital e loja virtual",
|
||||
description: caseStudy.metaDescription,
|
||||
url: pageUrl,
|
||||
image: `${pageUrl}/opengraph-image/${caseStudy.id}`,
|
||||
inLanguage: "pt-BR",
|
||||
creator: { "@id": SEO_IDS.person },
|
||||
sourceOrganization: {
|
||||
"@type": "Organization",
|
||||
name: "SEV Moda Íntima",
|
||||
url: "https://sevmodaintima.com.br",
|
||||
},
|
||||
keywords:
|
||||
"Next.js 16, TypeScript, PostgreSQL, Prisma, Tailwind CSS 4, Framer Motion",
|
||||
});
|
||||
});
|
||||
|
||||
it("builds a CreativeWork for every case, with the client only on client work", () => {
|
||||
for (const caseStudy of caseStudies) {
|
||||
const work = buildCaseJsonLd(caseStudy);
|
||||
expect(work).toMatchObject({
|
||||
"@type": "CreativeWork",
|
||||
url: `${siteUrl}/cases/${caseStudy.id}`,
|
||||
image: getCaseImageUrl(caseStudy.id),
|
||||
creator: { "@id": SEO_IDS.person },
|
||||
});
|
||||
expect(work.name).toContain(caseStudy.title);
|
||||
expect(work.description.length).toBeGreaterThan(40);
|
||||
expect(work.keywords).toBe(caseStudy.stack.join(", "));
|
||||
expect("sourceOrganization" in work).toBe(isClientCase(caseStudy));
|
||||
}
|
||||
});
|
||||
|
||||
it("omits the client address when a client case has no public site", () => {
|
||||
const work = buildCaseJsonLd(getCase("as-servicos")) as {
|
||||
url: string;
|
||||
sourceOrganization?: Record<string, unknown>;
|
||||
};
|
||||
expect(work.sourceOrganization).toStrictEqual({
|
||||
"@type": "Organization",
|
||||
name: "AS Serviços",
|
||||
});
|
||||
expect(work.url).toBe(`${siteUrl}/cases/as-servicos`);
|
||||
});
|
||||
|
||||
it("builds contiguous breadcrumbs from readonly tuples", () => {
|
||||
const pageUrl = `${siteUrl}/servicos/x`;
|
||||
const items = [
|
||||
|
||||
Reference in new issue
Block a user