diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-23 19:02:20 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-23 19:02:20 +0200 |
| commit | 5ebd7c14f7303a0feb8ec1d902ecd0e287d929c3 (patch) | |
| tree | 4e0e0352ac7598fa4e186230e93bcc4c1ac6b810 /src | |
| parent | 34e216546151eaf8a0a3cbb0bc8b65dae4c63bf2 (diff) | |
refactor(schema): use helpers function to avoid repeat between pages
Diffstat (limited to 'src')
| -rw-r--r-- | src/pages/article/[slug].tsx | 94 | ||||
| -rw-r--r-- | src/pages/blog/index.tsx | 49 | ||||
| -rw-r--r-- | src/pages/blog/page/[number].tsx | 49 | ||||
| -rw-r--r-- | src/pages/contact.tsx | 60 | ||||
| -rw-r--r-- | src/pages/cv.tsx | 61 | ||||
| -rw-r--r-- | src/pages/index.tsx | 26 | ||||
| -rw-r--r-- | src/pages/mentions-legales.tsx | 62 | ||||
| -rw-r--r-- | src/pages/projets/[slug].tsx | 60 | ||||
| -rw-r--r-- | src/pages/projets/index.tsx | 62 | ||||
| -rw-r--r-- | src/pages/recherche/index.tsx | 49 | ||||
| -rw-r--r-- | src/pages/sujet/[slug].tsx | 61 | ||||
| -rw-r--r-- | src/pages/thematique/[slug].tsx | 60 | ||||
| -rw-r--r-- | src/utils/helpers/schema-org.ts | 224 |
13 files changed, 468 insertions, 449 deletions
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 2878538..c72d48e 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -18,6 +18,12 @@ import { type NextPageWithLayout, } from '@ts/types/app'; import { loadTranslation, type Messages } from '@utils/helpers/i18n'; +import { + getBlogSchema, + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import usePrism, { type OptionalPrismPlugin } from '@utils/hooks/use-prism'; import useReadingTime from '@utils/hooks/use-reading-time'; @@ -29,7 +35,6 @@ import Script from 'next/script'; import { ParsedUrlQuery } from 'querystring'; import { HTMLAttributes } from 'react'; import { useIntl } from 'react-intl'; -import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts'; import useSWR from 'swr'; type ArticlePageProps = { @@ -108,64 +113,35 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ const { website } = useSettings(); const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - lastReviewed: dates.update, - name: seo.title, - description: seo.description, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const blogSchema: Blog = { - '@id': `${website.url}/#blog`, - '@type': 'Blog', - blogPost: { '@id': `${website.url}/#article` }, - isPartOf: { - '@id': `${pageUrl}`, - }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - }; - - const blogPostSchema: BlogPosting = { - '@id': `${website.url}/#article`, - '@type': 'BlogPosting', - name: title, + const webpageSchema = getWebPageSchema({ description: intro, - articleBody: content, - author: { '@id': `${website.url}/#branding` }, - commentCount: commentsCount, - copyrightYear: pagePublicationDate.getFullYear(), - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - discussionUrl: `${pageUrl}/#comments`, - editor: { '@id': `${website.url}/#branding` }, - headline: title, - image: cover?.src, - inLanguage: website.locales.default, - isPartOf: { - '@id': `${website.url}/blog`, - }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - thumbnailUrl: cover?.src, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema, blogPostSchema], - }; + locale: website.locales.default, + slug: asPath, + title, + updateDate: dates.update, + }); + const blogSchema = getBlogSchema({ + isSinglePage: true, + locale: website.locales.default, + slug: asPath, + }); + const blogPostSchema = getSinglePageSchema({ + commentsCount, + content, + cover: cover?.src, + dates, + description: intro, + id: 'article', + kind: 'post', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([ + webpageSchema, + blogSchema, + blogPostSchema, + ]); const prismPlugins: OptionalPrismPlugin[] = ['command-line', 'line-numbers']; const { attributes, className } = usePrism({ plugins: prismPlugins }); @@ -202,6 +178,8 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ prismClassNameReplacer ); + const pageUrl = `${website.url}${asPath}`; + return ( <> <Head> diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index dd04fad..cfd6f8c 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -23,6 +23,11 @@ import { getPageLinkFromRawData, getPostsList, } from '@utils/helpers/pages'; +import { + getBlogSchema, + getSchemaJson, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import usePagination from '@utils/hooks/use-pagination'; import useSettings from '@utils/hooks/use-settings'; @@ -31,7 +36,6 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; import { useIntl } from 'react-intl'; -import { Blog, Graph, WebPage } from 'schema-dts'; type BlogPageProps = { articles: EdgesResponse<RawArticle>; @@ -80,37 +84,18 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ }, { websiteName: website.name } ); - const pageUrl = `${website.url}${asPath}`; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: pageTitle, + const webpageSchema = getWebPageSchema({ description: pageDescription, - inLanguage: website.locales.default, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const blogSchema: Blog = { - '@id': `${website.url}/#blog`, - '@type': 'Blog', - author: { '@id': `${website.url}/#branding` }, - creator: { '@id': `${website.url}/#branding` }, - editor: { '@id': `${website.url}/#branding` }, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema], - }; + locale: website.locales.default, + slug: asPath, + title, + }); + const blogSchema = getBlogSchema({ + isSinglePage: false, + locale: website.locales.default, + slug: asPath, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); const { data, @@ -149,7 +134,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ <Head> <title>{pageTitle}</title> <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="website" /> <meta property="og:title" content={title} /> <meta property="og:description" content={pageDescription} /> diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx index 8f50ddd..78b1db4 100644 --- a/src/pages/blog/page/[number].tsx +++ b/src/pages/blog/page/[number].tsx @@ -26,6 +26,11 @@ import { getPageLinkFromRawData, getPostsList, } from '@utils/helpers/pages'; +import { + getBlogSchema, + getSchemaJson, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useRedirection from '@utils/hooks/use-redirection'; import useSettings from '@utils/hooks/use-settings'; @@ -35,7 +40,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import { ParsedUrlQuery } from 'querystring'; import { useIntl } from 'react-intl'; -import { Blog, Graph, WebPage } from 'schema-dts'; type BlogPageProps = { articles: EdgesResponse<RawArticle>; @@ -95,37 +99,18 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ }, { websiteName: website.name } ); - const pageUrl = `${website.url}${asPath}`; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: pageTitle, + const webpageSchema = getWebPageSchema({ description: pageDescription, - inLanguage: website.locales.default, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const blogSchema: Blog = { - '@id': `${website.url}/#blog`, - '@type': 'Blog', - author: { '@id': `${website.url}/#branding` }, - creator: { '@id': `${website.url}/#branding` }, - editor: { '@id': `${website.url}/#branding` }, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema], - }; + locale: website.locales.default, + slug: asPath, + title, + }); + const blogSchema = getBlogSchema({ + isSinglePage: false, + locale: website.locales.default, + slug: asPath, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); const thematicsListTitle = intl.formatMessage({ defaultMessage: 'Thematics', @@ -144,7 +129,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ <Head> <title>{pageTitle}</title> <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="website" /> <meta property="og:title" content={pageTitleWithPageNumber} /> <meta property="og:description" content={pageDescription} /> diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx index c0d6c79..2392fe2 100644 --- a/src/pages/contact.tsx +++ b/src/pages/contact.tsx @@ -6,10 +6,15 @@ import SocialMedia from '@components/organisms/widgets/social-media'; import { getLayout } from '@components/templates/layout/layout'; import PageLayout from '@components/templates/page/page-layout'; import { meta } from '@content/pages/contact.mdx'; -import styles from '@styles/pages/contact.module.scss'; import { sendMail } from '@services/graphql/contact'; +import styles from '@styles/pages/contact.module.scss'; import { type NextPageWithLayout } from '@ts/types/app'; import { loadTranslation } from '@utils/helpers/i18n'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { GetStaticProps } from 'next'; @@ -18,7 +23,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import { useState } from 'react'; import { useIntl } from 'react-intl'; -import { ContactPage as ContactPageSchema, Graph, WebPage } from 'schema-dts'; const ContactPage: NextPageWithLayout = () => { const { dates, intro, seo, title } = meta; @@ -36,43 +40,23 @@ const ContactPage: NextPageWithLayout = () => { const { website } = useSettings(); const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const contactSchema: ContactPageSchema = { - '@id': `${website.url}/#contact`, - '@type': 'ContactPage', - name: title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const contactSchema = getSinglePageSchema({ + dates, description: intro, - author: { '@id': `${website.url}/#branding` }, - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, contactSchema], - }; + id: 'contact', + kind: 'contact', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, contactSchema]); const widgets = [ <SocialMedia @@ -134,7 +118,7 @@ const ContactPage: NextPageWithLayout = () => { <Head> <title>{`${seo.title} - ${website.name}`}</title> <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="article" /> <meta property="og:title" content={title} /> <meta property="og:description" content={intro} /> diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index 3f035d8..4686505 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -11,6 +11,11 @@ import CVContent, { data, meta } from '@content/pages/cv.mdx'; import styles from '@styles/pages/cv.module.scss'; import { type NextPageWithLayout } from '@ts/types/app'; import { loadTranslation } from '@utils/helpers/i18n'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { NestedMDXComponents } from 'mdx/types'; @@ -20,7 +25,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import React, { ReactNode } from 'react'; import { useIntl } from 'react-intl'; -import { AboutPage, Graph, WebPage } from 'schema-dts'; /** * CV page. @@ -106,45 +110,24 @@ const CVPage: NextPageWithLayout = () => { ]; const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const cvSchema: AboutPage = { - '@id': `${website.url}/#cv`, - '@type': 'AboutPage', - name: seo.title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const cvSchema = getSinglePageSchema({ + cover: image.src, + dates, description: intro, - author: { '@id': `${website.url}/#branding` }, - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - image: image.src, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - thumbnailUrl: image.src, - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, cvSchema], - }; + id: 'cv', + kind: 'about', + locale: website.locales.default, + slug: asPath, + title: title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, cvSchema]); const components: NestedMDXComponents = { a: (props) => <Link external={true} {...props} />, @@ -171,7 +154,7 @@ const CVPage: NextPageWithLayout = () => { <Head> <title>{`${seo.title} - ${website.name}`}</title> <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="article" /> <meta property="og:title" content={title} /> <meta property="og:description" content={intro} /> diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a831ea3..6e9c4c6 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -19,6 +19,7 @@ import { getArticlesCard } from '@services/graphql/articles'; import styles from '@styles/pages/home.module.scss'; import { type ArticleCard, type NextPageWithLayout } from '@ts/types/app'; import { loadTranslation, type Messages } from '@utils/helpers/i18n'; +import { getSchemaJson, getWebPageSchema } from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { NestedMDXComponents } from 'mdx/types'; @@ -27,7 +28,6 @@ import Head from 'next/head'; import Script from 'next/script'; import { ReactElement } from 'react'; import { useIntl } from 'react-intl'; -import { Graph, WebPage } from 'schema-dts'; type HomeProps = { recentPosts: ArticleCard[]; @@ -306,25 +306,13 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { }, { websiteName: website.name } ); - - const webpageSchema: WebPage = { - '@id': `${website.url}/#home`, - '@type': 'WebPage', - name: pageTitle, + const webpageSchema = getWebPageSchema({ description: pageDescription, - author: { '@id': `${website.url}/#branding` }, - creator: { '@id': `${website.url}/#branding` }, - editor: { '@id': `${website.url}/#branding` }, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema], - }; + locale: website.locales.default, + slug: '', + title: pageTitle, + }); + const schemaJsonLd = getSchemaJson([webpageSchema]); return ( <> diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx index c8d1772..a58a850 100644 --- a/src/pages/mentions-legales.tsx +++ b/src/pages/mentions-legales.tsx @@ -7,6 +7,11 @@ import PageLayout, { import LegalNoticeContent, { meta } from '@content/pages/legal-notice.mdx'; import { type NextPageWithLayout } from '@ts/types/app'; import { loadTranslation } from '@utils/helpers/i18n'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { NestedMDXComponents } from 'mdx/types'; @@ -14,7 +19,6 @@ import { GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; -import { Article, Graph, WebPage } from 'schema-dts'; /** * Legal Notice page. @@ -44,47 +48,23 @@ const LegalNoticePage: NextPageWithLayout = () => { const { website } = useSettings(); const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const articleSchema: Article = { - '@id': `${website.url}/#legal-notice`, - '@type': 'Article', - name: title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const articleSchema = getSinglePageSchema({ + dates, description: intro, - author: { '@id': `${website.url}/#branding` }, - copyrightYear: pagePublicationDate.getFullYear(), - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - headline: title, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; + id: 'legal-notice', + kind: 'page', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]); return ( <PageLayout @@ -98,7 +78,7 @@ const LegalNoticePage: NextPageWithLayout = () => { <Head> <title>{`${seo.title} - ${website.name}`}</title> <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="article" /> <meta property="og:title" content={`${seo.title} - ${website.name}`} /> <meta property="og:description" content={intro} /> diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx index 27c715d..247f350 100644 --- a/src/pages/projets/[slug].tsx +++ b/src/pages/projets/[slug].tsx @@ -22,6 +22,11 @@ import { } from '@ts/types/app'; import { loadTranslation, type Messages } from '@utils/helpers/i18n'; import { getProjectData, getProjectFilenames } from '@utils/helpers/projects'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import { capitalize } from '@utils/helpers/strings'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useGithubApi, { type RepoData } from '@utils/hooks/use-github-api'; @@ -33,7 +38,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import { ComponentType } from 'react'; import { useIntl } from 'react-intl'; -import { Article, Graph, WebPage } from 'schema-dts'; type ProjectPageProps = { project: ProjectPreview; @@ -66,8 +70,6 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { const { website } = useSettings(); const { asPath } = useRouter(); const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; const headerMeta: PageLayoutProps['headerMeta'] = { publication: { date: dates.publication }, @@ -137,44 +139,24 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { technologies, }; - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - inLanguage: website.locales.default, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const articleSchema: Article = { - '@id': `${website.url}/project`, - '@type': 'Article', - name: title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const articleSchema = getSinglePageSchema({ + cover: `/projects/${id}.jpg`, + dates, description: intro, - author: { '@id': `${website.url}/#branding` }, - copyrightYear: pagePublicationDate.getFullYear(), - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - headline: title, - thumbnailUrl: `/projects/${id}.jpg`, - image: `/projects/${id}.jpg`, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; + id: 'project', + kind: 'page', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]); return ( <> diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx index 9ca289a..dbca019 100644 --- a/src/pages/projets/index.tsx +++ b/src/pages/projets/index.tsx @@ -9,6 +9,11 @@ import styles from '@styles/pages/projects.module.scss'; import { type NextPageWithLayout, type ProjectCard } from '@ts/types/app'; import { loadTranslation, type Messages } from '@utils/helpers/i18n'; import { getProjectsCard } from '@utils/helpers/projects'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { NestedMDXComponents } from 'mdx/types'; @@ -16,7 +21,6 @@ import { GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; -import { Article, Graph, WebPage } from 'schema-dts'; type ProjectsPageProps = { projects: ProjectCard[]; @@ -54,54 +58,30 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => { const { website } = useSettings(); const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const articleSchema: Article = { - '@id': `${website.url}/#projects`, - '@type': 'Article', - name: meta.title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const articleSchema = getSinglePageSchema({ + dates, description: seo.description, - author: { '@id': `${website.url}/#branding` }, - copyrightYear: pagePublicationDate.getFullYear(), - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - headline: meta.title, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; + id: 'projects', + kind: 'page', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]); return ( <> <Head> <title>{`${seo.title} - ${website.name}`}</title> <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="article" /> <meta property="og:title" content={`${seo.title} - ${website.name}`} /> <meta property="og:description" content={seo.description} /> diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index c69d931..ab619fb 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -22,6 +22,11 @@ import { getPageLinkFromRawData, getPostsList, } from '@utils/helpers/pages'; +import { + getBlogSchema, + getSchemaJson, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useDataFromAPI from '@utils/hooks/use-data-from-api'; import usePagination from '@utils/hooks/use-pagination'; @@ -31,7 +36,6 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; import { useIntl } from 'react-intl'; -import { Blog, Graph, WebPage } from 'schema-dts'; type SearchPageProps = { thematicsList: RawThematicPreview[]; @@ -87,37 +91,18 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ }, { websiteName: website.name } ); - const pageUrl = `${website.url}${asPath}`; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: pageTitle, + const webpageSchema = getWebPageSchema({ description: pageDescription, - inLanguage: website.locales.default, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - isPartOf: { - '@id': `${website.url}`, - }, - }; - - const blogSchema: Blog = { - '@id': `${website.url}/#blog`, - '@type': 'Blog', - author: { '@id': `${website.url}/#branding` }, - creator: { '@id': `${website.url}/#branding` }, - editor: { '@id': `${website.url}/#branding` }, - inLanguage: website.locales.default, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema], - }; + locale: website.locales.default, + slug: asPath, + title: pageTitle, + }); + const blogSchema = getBlogSchema({ + isSinglePage: false, + locale: website.locales.default, + slug: asPath, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); const { data, @@ -161,7 +146,7 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ <Head> <title>{pageTitle}</title> <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="website" /> <meta property="og:title" content={title} /> <meta property="og:description" content={pageDescription} /> diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index 95843cd..838f009 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -24,6 +24,11 @@ import { getPageLinkFromRawData, getPostsWithUrl, } from '@utils/helpers/pages'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { GetStaticPaths, GetStaticProps } from 'next'; @@ -32,7 +37,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import { ParsedUrlQuery } from 'querystring'; import { useIntl } from 'react-intl'; -import { Article as ArticleSchema, Graph, WebPage } from 'schema-dts'; export type TopicPageProps = { currentTopic: Topic; @@ -68,45 +72,24 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ const { website } = useSettings(); const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - inLanguage: website.locales.default, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - }; - - const articleSchema: ArticleSchema = { - '@id': `${website.url}/#topic`, - '@type': 'Article', - name: title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const articleSchema = getSinglePageSchema({ + cover: cover?.src, + dates, description: intro, - author: { '@id': `${website.url}/#branding` }, - copyrightYear: pagePublicationDate.getFullYear(), - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - headline: title, - inLanguage: website.locales.default, - isPartOf: { '@id': `${website.url}/blog` }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - subjectOf: { '@id': `${website.url}/blog` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; + id: 'topic', + kind: 'page', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]); const topicsListTitle = intl.formatMessage({ defaultMessage: 'Other topics', @@ -134,7 +117,7 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ <Head> <title>{seo.title}</title> <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="article" /> <meta property="og:title" content={title} /> <meta property="og:description" content={intro} /> diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index a91483e..84ab134 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -22,6 +22,11 @@ import { getPageLinkFromRawData, getPostsWithUrl, } from '@utils/helpers/pages'; +import { + getSchemaJson, + getSinglePageSchema, + getWebPageSchema, +} from '@utils/helpers/schema-org'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import useSettings from '@utils/hooks/use-settings'; import { GetStaticPaths, GetStaticProps } from 'next'; @@ -30,7 +35,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import { ParsedUrlQuery } from 'querystring'; import { useIntl } from 'react-intl'; -import { Article as ArticleSchema, Graph, WebPage } from 'schema-dts'; export type ThematicPageProps = { currentThematic: Thematic; @@ -58,45 +62,23 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ const { website } = useSettings(); const { asPath } = useRouter(); - const pageUrl = `${website.url}${asPath}`; - const pagePublicationDate = new Date(dates.publication); - const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${website.url}/#breadcrumb` }, - name: seo.title, + const webpageSchema = getWebPageSchema({ description: seo.description, - inLanguage: website.locales.default, - reviewedBy: { '@id': `${website.url}/#branding` }, - url: `${website.url}`, - }; - - const articleSchema: ArticleSchema = { - '@id': `${website.url}/#thematic`, - '@type': 'Article', - name: title, + locale: website.locales.default, + slug: asPath, + title: seo.title, + updateDate: dates.update, + }); + const articleSchema = getSinglePageSchema({ + dates, description: intro, - author: { '@id': `${website.url}/#branding` }, - copyrightYear: pagePublicationDate.getFullYear(), - creator: { '@id': `${website.url}/#branding` }, - dateCreated: pagePublicationDate.toISOString(), - dateModified: pageUpdateDate && pageUpdateDate.toISOString(), - datePublished: pagePublicationDate.toISOString(), - editor: { '@id': `${website.url}/#branding` }, - headline: title, - inLanguage: website.locales.default, - isPartOf: { '@id': `${website.url}/blog` }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - subjectOf: { '@id': `${website.url}/blog` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; + id: 'thematic', + kind: 'page', + locale: website.locales.default, + slug: asPath, + title, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]); const thematicsListTitle = intl.formatMessage({ defaultMessage: 'Other thematics', @@ -115,7 +97,7 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ <Head> <title>{seo.title}</title> <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:url" content={`${website.url}${asPath}`} /> <meta property="og:type" content="article" /> <meta property="og:title" content={title} /> <meta property="og:description" content={intro} /> diff --git a/src/utils/helpers/schema-org.ts b/src/utils/helpers/schema-org.ts new file mode 100644 index 0000000..cdace00 --- /dev/null +++ b/src/utils/helpers/schema-org.ts @@ -0,0 +1,224 @@ +import { Dates } from '@ts/types/app'; +import { settings } from '@utils/config'; +import { + AboutPage, + Article, + Blog, + BlogPosting, + ContactPage, + Graph, + WebPage, +} from 'schema-dts'; + +export type GetBlogSchemaProps = { + /** + * True if the page is part of the blog. + */ + isSinglePage: boolean; + /** + * The page locale. + */ + locale: string; + /** + * The page slug with a leading slash. + */ + slug: string; +}; + +/** + * Retrieve the JSON for Blog schema. + * + * @param props - The page data. + * @returns {Blog} The JSON for Blog schema. + */ +export const getBlogSchema = ({ + isSinglePage, + locale, + slug, +}: GetBlogSchemaProps): Blog => { + return { + '@id': `${settings.url}/#blog`, + '@type': 'Blog', + author: { '@id': `${settings.url}/#branding` }, + creator: { '@id': `${settings.url}/#branding` }, + editor: { '@id': `${settings.url}/#branding` }, + blogPost: isSinglePage ? { '@id': `${settings.url}/#article` } : undefined, + inLanguage: locale, + isPartOf: isSinglePage + ? { + '@id': `${settings.url}${slug}`, + } + : undefined, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + mainEntityOfPage: isSinglePage + ? undefined + : { '@id': `${settings.url}${slug}` }, + }; +}; + +export type SinglePageSchemaReturn = { + about: AboutPage; + contact: ContactPage; + page: Article; + post: BlogPosting; +}; + +export type SinglePageSchemaKind = keyof SinglePageSchemaReturn; + +export type GetSinglePageSchemaProps<T extends SinglePageSchemaKind> = { + /** + * The number of comments. + */ + commentsCount?: number; + /** + * The page content. + */ + content?: string; + /** + * The url of the cover. + */ + cover?: string; + /** + * The page dates. + */ + dates: Dates; + /** + * The page description. + */ + description: string; + /** + * The page id. + */ + id: string; + /** + * The page kind. + */ + kind: T; + /** + * The page locale. + */ + locale: string; + /** + * The page slug with a leading slash. + */ + slug: string; + /** + * The page title. + */ + title: string; +}; + +/** + * Retrieve the JSON schema depending on the page kind. + * + * @param props - The page data. + * @returns {SinglePageSchemaReturn[T]} - Either AboutPage, ContactPage, Article or BlogPosting schema. + */ +export const getSinglePageSchema = <T extends SinglePageSchemaKind>({ + commentsCount, + content, + cover, + dates, + description, + id, + kind, + locale, + title, + slug, +}: GetSinglePageSchemaProps<T>): SinglePageSchemaReturn[T] => { + const publicationDate = new Date(dates.publication); + const updateDate = dates.update ? new Date(dates.update) : undefined; + const singlePageSchemaType = { + about: 'AboutPage', + contact: 'ContactPage', + page: 'Article', + post: 'BlogPosting', + }; + + return { + '@id': `${settings.url}/#${id}`, + '@type': singlePageSchemaType[kind], + name: title, + description, + articleBody: content, + author: { '@id': `${settings.url}/#branding` }, + commentCount: commentsCount, + copyrightYear: publicationDate.getFullYear(), + creator: { '@id': `${settings.url}/#branding` }, + dateCreated: publicationDate.toISOString(), + dateModified: updateDate && updateDate.toISOString(), + datePublished: publicationDate.toISOString(), + editor: { '@id': `${settings.url}/#branding` }, + headline: title, + image: cover, + inLanguage: locale, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + thumbnailUrl: cover, + isPartOf: + kind === 'post' + ? { + '@id': `${settings.url}/blog`, + } + : undefined, + mainEntityOfPage: { '@id': `${settings.url}${slug}` }, + } as SinglePageSchemaReturn[T]; +}; + +export type GetWebPageSchemaProps = { + /** + * The page description. + */ + description: string; + /** + * The page locale. + */ + locale: string; + /** + * The page slug. + */ + slug: string; + /** + * The page title. + */ + title: string; + /** + * The page last update. + */ + updateDate?: string; +}; + +/** + * Retrieve the JSON for WebPage schema. + * + * @param props - The page data. + * @returns {WebPage} The JSON for WebPage schema. + */ +export const getWebPageSchema = ({ + description, + locale, + slug, + title, + updateDate, +}: GetWebPageSchemaProps): WebPage => { + return { + '@id': `${settings.url}${slug}`, + '@type': 'WebPage', + breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, + lastReviewed: updateDate, + name: title, + description: description, + inLanguage: locale, + reviewedBy: { '@id': `${settings.url}/#branding` }, + url: `${settings.url}${slug}`, + isPartOf: { + '@id': `${settings.url}`, + }, + }; +}; + +export const getSchemaJson = (graphs: Graph['@graph']): Graph => { + return { + '@context': 'https://schema.org', + '@graph': graphs, + }; +}; |
