Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: some SEO artefacts #133

Merged
merged 2 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .ts-prune/artifacts/tsprune-false-positives.conf
Original file line number Diff line number Diff line change
Expand Up @@ -324,14 +324,15 @@ src/types/adapters/PageAdapter.ts:12 - TopLevelRoot (used in module)
# DO NOT DELETE THIS BEFORE A DOUBLE-CHECK!
# Try to ts-prune with and without the .next folder, and see if
# those false positives are always evaluated as outdated.
# 📊 Entries: 41
# 📊 Entries: 42
#=================================================================

#------------------------
# **** II. 1) NEXT AUTH
#------------------------
#------------------------------
# **** II. 1) NEXT AUTH / API
#------------------------------
src/app/api/auth/[...nextauth]/route.ts:8 - POST
src/app/api/auth/[...nextauth]/route.ts:8 - GET
src/app/api/error/route.ts:4 - POST

#-------------------
# **** II. 2) NEXT
Expand Down
9 changes: 8 additions & 1 deletion interop/config/contentlayer/adapters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DocumentTypeDef } from 'contentlayer/source-files';
import type { NestedUnnamedTypeDef, DocumentTypeDef, NestedTypeDef, NestedType } from 'contentlayer/source-files';

// * ... Adapter (narrowing)
// * ... Also made because importing `defineDocumentType` from `contentlayer/source-files` is not possible (loaders hell)
Expand All @@ -7,3 +7,10 @@ export const defineDocumentType = (def: () => DocumentTypeDef<string>) =>
type: 'document',
def
}) as const;

// * ... Adapter (narrowing)
// * ... Also made because importing `defineNestedType` from `contentlayer/source-files` is not possible (loaders hell)
export const defineNestedType = (def: () => NestedTypeDef<string> | NestedUnnamedTypeDef): NestedType => ({
type: 'nested',
def
});
16 changes: 13 additions & 3 deletions interop/config/contentlayer/contentlayerConfigTweakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
buildPageUrl
} from '../../lib/builders';
import { blogTagOptions } from './blog/blogTags';
import SEO from './nested-types/SEO';

export const PAGES_FOLDER = 'pages';
export const BLOG_POSTS_FOLDER = 'blog';
Expand Down Expand Up @@ -107,6 +108,8 @@ const _ALL_BLOG_FIELDS = {
required: true
},

seo: { required: false, type: 'nested', of: SEO },

date: {
required: true,
type: 'date'
Expand Down Expand Up @@ -151,6 +154,8 @@ const _ALL_LANDING_PAGES_FIELDS = {
required: true
},

seo: { required: false, type: 'nested', of: SEO },

url: {
type: 'string',
required: true
Expand Down Expand Up @@ -189,6 +194,8 @@ const _ALL_PAGES_FIELDS = {
required: true
},

seo: { required: false, type: 'nested', of: SEO },

url: {
type: 'string',
required: true
Expand Down Expand Up @@ -217,7 +224,8 @@ export const BLOG_DOCUMENTS_FIELDS = {
draft: _ALL_BLOG_FIELDS.draft,
title: _ALL_BLOG_FIELDS.title,
tags: _ALL_BLOG_FIELDS.tags,
date: _ALL_BLOG_FIELDS.date
date: _ALL_BLOG_FIELDS.date,
seo: _ALL_BLOG_FIELDS.seo
} as const satisfies DocumentsFields<_AllBlogFields, _BlogDocumentsComputedFieldsKeys>;

export const BLOG_POST_SCHEMA_CONFIG: ContentlayerDocumentsConfigType<BlogPostSchemaKey> = {
Expand All @@ -244,7 +252,8 @@ export const LANDING_PAGES_DOCUMENTS_FIELDS = {
doNotExcludeFromLocalSearch: _ALL_LANDING_PAGES_FIELDS.doNotExcludeFromLocalSearch,
metadescription: _ALL_LANDING_PAGES_FIELDS.metadescription,
draft: _ALL_LANDING_PAGES_FIELDS.draft,
title: _ALL_LANDING_PAGES_FIELDS.title
title: _ALL_LANDING_PAGES_FIELDS.title,
seo: _ALL_LANDING_PAGES_FIELDS.seo
} as const satisfies DocumentsFields<_AllLandingPagesFields, _LandingPagesDocumentsComputedFieldsKeys>;

/* v8 ignore start */
Expand All @@ -263,7 +272,8 @@ export const PAGES_DOCUMENTS_COMPUTED_FIELDS = {
export const PAGES_DOCUMENTS_FIELDS = {
metadescription: _ALL_PAGES_FIELDS.metadescription,
draft: _ALL_PAGES_FIELDS.draft,
title: _ALL_PAGES_FIELDS.title
title: _ALL_PAGES_FIELDS.title,
seo: _ALL_PAGES_FIELDS.seo
} as const satisfies DocumentsFields<_AllPagesFields, _PagesDocumentsComputedFieldsKeys>;

type BlogPostSchemaKey = 'BlogPostSchema';
Expand Down
98 changes: 98 additions & 0 deletions interop/config/contentlayer/nested-types/SEO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { defineNestedType } from '../adapters';

// * ... Inspired from https://github.com/contentlayerdev/contentlayer/blob/main/examples/archive/playground-azimuth/src/contentlayer/nested/SEO.ts

const Robots = defineNestedType(() => ({
fields: {
nositelinkssearchbox: { required: false, type: 'boolean', default: false },
indexifembedded: { required: false, type: 'boolean', default: true },
noimageindex: { required: false, type: 'boolean', default: false },
notranslate: { required: false, type: 'boolean', default: false },
noarchive: { required: false, type: 'boolean', default: false },
nosnippet: { required: false, type: 'boolean', default: false },
nocache: { required: false, type: 'boolean', default: false },
follow: { required: false, type: 'boolean', default: true },
index: { required: false, type: 'boolean', default: true }
}
}));

const AlternateLinkDescriptor = defineNestedType(() => ({
fields: {
title: { required: false, type: 'string' },
url: { required: true, type: 'string' }
}
}));

const Alternates = defineNestedType(() => ({
fields: {
canonical: { of: AlternateLinkDescriptor, required: false, type: 'nested' }
}
}));

const OGImageDescriptor = defineNestedType(() => ({
fields: {
secureUrl: { required: false, type: 'string' },
height: { required: false, type: 'number' },
width: { required: false, type: 'number' },
type: { required: false, type: 'string' },
alt: { required: false, type: 'string' },
url: { required: true, type: 'string' }
}
}));

const OGAudioDescriptor = defineNestedType(() => ({
fields: {
secureUrl: { required: false, type: 'string' },
type: { required: false, type: 'string' },
url: { required: true, type: 'string' }
}
}));

const OGVideoDescriptor = defineNestedType(() => ({
fields: {
secureUrl: { required: false, type: 'string' },
height: { required: false, type: 'number' },
width: { required: false, type: 'number' },
type: { required: false, type: 'string' },
url: { required: true, type: 'string' }
}
}));

const OpenGraph = defineNestedType(() => ({
fields: {
images: { of: OGImageDescriptor, required: false, type: 'list' },
videos: { of: OGVideoDescriptor, required: false, type: 'list' },
audio: { of: OGAudioDescriptor, required: false, type: 'list' },
description: { required: false, type: 'string' },
title: { required: false, type: 'string' }
}
}));

const SEO = defineNestedType(() => ({
fields: {
alternates: {
description: 'The canonical and alternate URLs for the document',
required: false,
type: 'nested',
of: Alternates
},

robots: {
description: 'The items that go into the <meta name="robots"> tag',
required: false,
type: 'nested',
of: Robots
},

openGraph: {
description: 'The Open Graph data',
required: false,
type: 'nested',
of: OpenGraph
}
},

name: 'SEO'
}));

export default SEO;
6 changes: 3 additions & 3 deletions src/app/[locale]/(pages)/(withfooter)/[...path]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PageProps } from '@/types/Page';

import { getPagesStaticParams, getPagesMetadatas } from '@/lib/pages/staticGeneration';
import { getPageStaticParams, getPageMetadatas } from '@/lib/pages/staticGeneration';
import isSkippedPath from '@/lib/pages/static/helpers/isSkippedPath';
import { getPageByLanguageAndPathUnstrict } from '@/lib/pages/api';
import { setStaticParamsLocale } from 'next-international/server';
Expand All @@ -12,12 +12,12 @@ import { notFound } from 'next/navigation';
import { cn } from '@/lib/tailwind';

export async function generateMetadata({ params }: PageProps) {
const metadatas = await getPagesMetadatas({ params });
const metadatas = await getPageMetadatas({ params });
return metadatas;
}

export function generateStaticParams() {
const staticParams = getPagesStaticParams();
const staticParams = getPageStaticParams();
return staticParams;
}

Expand Down
10 changes: 10 additions & 0 deletions src/app/api/error/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* v8 ignore start */
// Stryker disable all

export async function POST(request: Request) {
const error = await request.json();
console.warn(error);
}

// Stryker restore all
/* v8 ignore stop */
14 changes: 11 additions & 3 deletions src/components/layouts/navbar/search/ProgressiveResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ const ProgressiveResults: FunctionComponent<ProgressiveResultsProps> = ({
const intervalMs: MsValue = 250;
let isComputing = false;

// {ToDo} Remove this line
// eslint-disable-next-line
let retryInterval: MaybeNull<NodeJS.Timeout> = setInterval(async () => {
function disposeRetryInterval() {
// eslint-disable-next-line no-magic-numbers
Expand All @@ -148,12 +150,18 @@ const ProgressiveResults: FunctionComponent<ProgressiveResultsProps> = ({
try {
if (isComputing) return;
isComputing = true;
await throttledComputeAndSetResults();
disposeRetryInterval();
throw new Error('lol'); // {ToDo} Remove this line and uncomment the next lines
// await throttledComputeAndSetResults();
// disposeRetryInterval();
} catch {
retries++;
if (maxRetries >= retries) {
// {ToDo} This should be logged
fetch('/api/error', {
body: JSON.stringify({ message: 'pagefindIntegrationError' }),
headers: { 'Content-Type': 'application/json' },
method: 'POST'
});

toast({
description: globalT(`${i18ns.brokenPagefindIntegrationError}.message`),
title: globalT(`${i18ns.brokenPagefindIntegrationError}.title`),
Expand Down
14 changes: 11 additions & 3 deletions src/lib/blog/staticGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
BlogPostType
} from '@/types/Blog';
import type { MaybeNull } from '@rtm/shared-types/CustomUtilityTypes';
import type { Metadata } from 'next';

import buildPageTitle from '@rtm/shared-lib/portable/str/buildPageTitle';
import BlogTaxonomy from '##/config/taxonomies/blog';
Expand Down Expand Up @@ -51,7 +52,7 @@ export async function getBlogSubcategoryMetadatas({ params }: BlogSubcategoryPag
return { description, title };
}

export async function getBlogPostMetadatas({ params }: BlogPostPageProps) {
export async function getBlogPostMetadatas({ params }: BlogPostPageProps): Promise<Metadata> {
const [category, subcategory, slug, language] = [
params[BlogTaxonomy.CATEGORY],
params[BlogTaxonomy.SUBCATEGORY],
Expand All @@ -67,8 +68,15 @@ export async function getBlogPostMetadatas({ params }: BlogPostPageProps) {
if (!isValidBlogCategoryAndSubcategoryPair(category, subcategory, language)) return {};

const title = buildPageTitle(globalT(`${i18ns.vocab}.brand-short`), currentPost.title);
const { metadescription: description } = post as BlogPostType;
return { description, title };
const { metadescription: description, seo } = currentPost;

// {ToDo} Generate languages alternates
// https://github.com/Tirraa/dashboard_rtm/issues/58#issuecomment-2103311665

if (seo === undefined) return { description, title };

const { alternates, openGraph, robots } = seo;
return { description, alternates, openGraph, robots, title };
}

export { blogSubcategoryGuard, blogCategoryGuard, blogPostGuard };
Expand Down
10 changes: 8 additions & 2 deletions src/lib/landingPages/staticGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ export async function getLandingPageMetadatas({ params }: LandingPageProps) {
if (!lp) notFound();

const globalT = await getServerSideI18n();
const { metadescription: description, title: lpTitle } = lp;
const { metadescription: description, title: lpTitle, seo } = lp;

const { vocab } = i18ns;
const title = buildPageTitle(globalT(`${vocab}.brand-short`), lpTitle);

return { description, title };
// {ToDo} Generate languages alternates
// https://github.com/Tirraa/dashboard_rtm/issues/58#issuecomment-2103311665

if (seo === undefined) return { description, title };

const { alternates, openGraph, robots } = seo;
return { description, alternates, openGraph, robots, title };
}

// Stryker restore all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PageTaxonomy from '##/config/taxonomies/pages';
import I18nTaxonomy from '##/config/taxonomies/i18n';
import { describe, expect, it, vi } from 'vitest';

import getPagesStaticParams from '../getPagesStaticParams';
import getPageStaticParams from '../getPageStaticParams';

vi.mock('../../../../../interop/config/i18n', async (orgImport) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
Expand Down Expand Up @@ -250,9 +250,9 @@ vi.mock('@/config/pages', async (orgImport) => {
};
});

describe('getPagesStaticParams', () => {
describe('getPageStaticParams', () => {
it('should return static params according to the allPages mock', () => {
const staticParams = getPagesStaticParams();
const staticParams = getPageStaticParams();

expect(staticParams).toStrictEqual([
{ [PageTaxonomy.PATH]: ['page-00'], [I18nTaxonomy.LANGUAGE]: 'fr' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PageTaxonomy from '##/config/taxonomies/pages';
import I18nTaxonomy from '##/config/taxonomies/i18n';
import { describe, expect, it, vi } from 'vitest';

import getPagesStaticParams from '../getPagesStaticParams';
import getPageStaticParams from '../getPageStaticParams';

vi.mock('../../../../../interop/config/i18n', async (orgImport) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
Expand Down Expand Up @@ -249,9 +249,9 @@ vi.mock('@/config/pages', async (orgImport) => {
};
});

describe('getPagesStaticParams', () => {
describe('getPageStaticParams', () => {
it('should return static params according to the allPages mock', () => {
const staticParams = getPagesStaticParams();
const staticParams = getPageStaticParams();

expect(staticParams).toStrictEqual([
{ [PageTaxonomy.PATH]: ['page-00'], [I18nTaxonomy.LANGUAGE]: 'fr' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PageTaxonomy from '##/config/taxonomies/pages';
import I18nTaxonomy from '##/config/taxonomies/i18n';
import { describe, expect, it, vi } from 'vitest';

import getPagesStaticParams from '../getPagesStaticParams';
import getPageStaticParams from '../getPageStaticParams';

vi.mock('../../../../../interop/config/i18n', async (orgImport) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
Expand Down Expand Up @@ -369,9 +369,9 @@ vi.mock('@/config/pages', async (orgImport) => {
};
});

describe('getPagesStaticParams (index notation)', () => {
describe('getPageStaticParams (index notation)', () => {
it('should return static params according to the allPages mock', () => {
const staticParams = getPagesStaticParams();
const staticParams = getPageStaticParams();

expect(staticParams).toStrictEqual([
{ [PageTaxonomy.PATH]: ['index'], [I18nTaxonomy.LANGUAGE]: 'fr' },
Expand Down
Loading