Skip to content

Commit

Permalink
refactor Canonical URL generator
Browse files Browse the repository at this point in the history
  • Loading branch information
tvikito committed Oct 3, 2023
1 parent e64270a commit a1dc0f4
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 56 deletions.
19 changes: 10 additions & 9 deletions storefront/components/Basic/Head/SeoMeta.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logMessage from 'helpers/errors/logMessage';
import { CanonicalQueryParameters } from 'helpers/seo/generateCanonicalUrl';
import useSeo from 'hooks/seo/useSeo';
import { useDomainConfig } from 'hooks/useDomainConfig';
import Head from 'next/head';
Expand All @@ -8,22 +9,24 @@ import { useEffect, useState } from 'react';
type SeoMetaProps = {
defaultTitle?: string | null;
defaultDescription?: string | null;
canonicalQueryParams?: CanonicalQueryParameters;
};

export const SeoMeta: FC<SeoMetaProps> = ({ defaultTitle, defaultDescription }) => {
export const SeoMeta: FC<SeoMetaProps> = ({ defaultTitle, defaultDescription, canonicalQueryParams }) => {
const [areMissingRequiredTagsReported, setAreMissingRequiredTagsReported] = useState(false);

const { title, titleSuffix, description, ogTitle, ogDescription, ogImageUrl, canonicalUrl } = useSeo({
defaultTitle,
defaultDescription,
canonicalQueryParams,
});

const currentUri = useRouter().asPath;
const { url } = useDomainConfig();
const currentUrlWithDomain = url.substring(0, url.length - 1) + currentUri;

useEffect(() => {
if (title === null && !areMissingRequiredTagsReported) {
if (!title && !areMissingRequiredTagsReported) {
logMessage('Missing required tags', [
{
key: 'tags',
Expand All @@ -37,13 +40,11 @@ export const SeoMeta: FC<SeoMetaProps> = ({ defaultTitle, defaultDescription })
return (
<Head>
<title>{`${title} ${titleSuffix}`}</title>
{description !== null && <meta name="description" content={description} />}
{ogTitle !== null && <meta name="og:title" content={ogTitle} />}
{ogDescription !== null && <meta name="og:description" content={ogDescription} />}
{ogImageUrl !== null && <meta property="og:image" content={ogImageUrl} />}
{canonicalUrl !== null && canonicalUrl !== currentUrlWithDomain && (
<link rel="canonical" href={canonicalUrl} />
)}
{description && <meta name="description" content={description} />}
{ogTitle && <meta name="og:title" content={ogTitle} />}
{ogDescription && <meta name="og:description" content={ogDescription} />}
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
{canonicalUrl && canonicalUrl !== currentUrlWithDomain && <link rel="canonical" href={canonicalUrl} />}
</Head>
);
};
13 changes: 11 additions & 2 deletions storefront/components/Layout/CommonLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,26 @@ import { SeoMeta } from 'components/Basic/Head/SeoMeta';
import { Adverts } from 'components/Blocks/Adverts/Adverts';
import { Breadcrumbs } from './Breadcrumbs/Breadcrumbs';
import { FriendlyPagesTypesKeys } from 'types/friendlyUrl';
import { CanonicalQueryParameters } from 'helpers/seo/generateCanonicalUrl';

type CommonLayoutProps = {
title?: string | null;
description?: string | null;
breadcrumbs?: BreadcrumbFragmentApi[];
breadcrumbsType?: FriendlyPagesTypesKeys;
canonicalQueryParams?: CanonicalQueryParameters;
};

export const CommonLayout: FC<CommonLayoutProps> = ({ children, description, title, breadcrumbs, breadcrumbsType }) => (
export const CommonLayout: FC<CommonLayoutProps> = ({
children,
description,
title,
breadcrumbs,
breadcrumbsType,
canonicalQueryParams,
}) => (
<>
<SeoMeta defaultTitle={title} defaultDescription={description} />
<SeoMeta defaultTitle={title} defaultDescription={description} canonicalQueryParams={canonicalQueryParams} />

<NotificationBars />

Expand Down
7 changes: 0 additions & 7 deletions storefront/helpers/queryParamNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,3 @@ export const LOAD_MORE_QUERY_PARAMETER_NAME = 'lm' as const;
export const FILTER_QUERY_PARAMETER_NAME = 'filter' as const;
export const SORT_QUERY_PARAMETER_NAME = 'sort' as const;
export const SLUG_TYPE_QUERY_PARAMETER_NAME = 'slugType' as const;
export const INTERNAL_QUERY_PARAMETERS = [
SEARCH_QUERY_PARAMETER_NAME,
PAGE_QUERY_PARAMETER_NAME,
FILTER_QUERY_PARAMETER_NAME,
SORT_QUERY_PARAMETER_NAME,
SLUG_TYPE_QUERY_PARAMETER_NAME,
];
46 changes: 31 additions & 15 deletions storefront/helpers/seo/generateCanonicalUrl.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,50 @@
import {
getQueryWithoutSlugTypeParameterFromParsedUrlQuery,
getUrlWithoutGetParameters,
} from 'helpers/parsing/urlParsing';
import { getUrlWithoutGetParameters } from 'helpers/parsing/urlParsing';
import { getStringWithoutTrailingSlash } from 'helpers/parsing/stringWIthoutSlash';
import { INTERNAL_QUERY_PARAMETERS } from 'helpers/queryParamNames';
import { NextRouter } from 'next/router';
import {
PAGE_QUERY_PARAMETER_NAME,
SEARCH_QUERY_PARAMETER_NAME,
FILTER_QUERY_PARAMETER_NAME,
SORT_QUERY_PARAMETER_NAME,
} from 'helpers/queryParamNames';

const DEFAULT_CANONICAL_QUERY_PARAMS = [
PAGE_QUERY_PARAMETER_NAME,
SEARCH_QUERY_PARAMETER_NAME,
FILTER_QUERY_PARAMETER_NAME,
SORT_QUERY_PARAMETER_NAME,
] as const;

export type CanonicalQueryParameters = (typeof DEFAULT_CANONICAL_QUERY_PARAMS)[number][];

export const generateCanonicalUrl = (router: NextRouter, url: string): string | null => {
export const generateCanonicalUrl = (
router: NextRouter,
url: string,
canonicalQueryParams?: CanonicalQueryParameters,
): string | null => {
const newQueryOverwrite: Record<string, string> = {};
const queryWithoutAllParameter = getQueryWithoutSlugTypeParameterFromParsedUrlQuery(router.query);
const queries = router.query;

for (const queryParam in queryWithoutAllParameter) {
if ((INTERNAL_QUERY_PARAMETERS as string[]).includes(queryParam)) {
const queryParamValue = queryWithoutAllParameter[queryParam]?.toString();
for (const queryParam in queries) {
if ((canonicalQueryParams || DEFAULT_CANONICAL_QUERY_PARAMS).includes(queryParam as any)) {
const queryParamValue = queries[queryParam]?.toString();

if (queryParamValue !== undefined) {
if (queryParamValue) {
newQueryOverwrite[queryParam] = queryParamValue;
}
}
}

if (JSON.stringify(newQueryOverwrite) === JSON.stringify(queryWithoutAllParameter)) {
if (JSON.stringify(newQueryOverwrite) === JSON.stringify(queries)) {
return null;
}

const queryParams = new URLSearchParams(newQueryOverwrite).toString();
const canonicalUrl = `${getStringWithoutTrailingSlash(url)}${getUrlWithoutGetParameters(router.asPath)}`;

if (queryParams.length === 0) {
return `${getStringWithoutTrailingSlash(url)}${getUrlWithoutGetParameters(router.asPath)}`;
if (!queryParams.length) {
return canonicalUrl;
}

return `${getStringWithoutTrailingSlash(url)}${getUrlWithoutGetParameters(router.asPath)}?${queryParams}`;
return `${canonicalUrl}?${queryParams}`;
};
43 changes: 21 additions & 22 deletions storefront/hooks/seo/useSeo.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { useSeoPageQueryApi, useSettingsQueryApi } from 'graphql/generated';
import { extractSeoPageSlugFromUrl } from 'helpers/seo/extractSeoPageSlugFromUrl';
import { generateCanonicalUrl } from 'helpers/seo/generateCanonicalUrl';
import { CanonicalQueryParameters, generateCanonicalUrl } from 'helpers/seo/generateCanonicalUrl';
import { useDomainConfig } from 'hooks/useDomainConfig';
import { useRouter } from 'next/router';
import { useMemo } from 'react';

type UseSeoHookReturn = {
title: string | null;
titleSuffix: string | null;
description: string | null;
ogTitle: string | null;
ogDescription: string | null;
ogImageUrl: string | null;
canonicalUrl: string | null;
title: string | null | undefined;
titleSuffix: string | null | undefined;
description: string | null | undefined;
ogTitle: string | null | undefined;
ogDescription: string | null | undefined;
ogImageUrl: string | null | undefined;
canonicalUrl: string | null | undefined;
};

type UseSeoHookProps = {
defaultTitle?: string | null;
defaultDescription?: string | null;
canonicalQueryParams?: CanonicalQueryParameters;
};

const useSeo = ({ defaultTitle, defaultDescription }: UseSeoHookProps): UseSeoHookReturn => {
const useSeo = ({ defaultTitle, defaultDescription, canonicalQueryParams }: UseSeoHookProps): UseSeoHookReturn => {
const { url } = useDomainConfig();
const router = useRouter();

Expand All @@ -33,23 +34,21 @@ const useSeo = ({ defaultTitle, defaultDescription }: UseSeoHookProps): UseSeoHo
variables: {
pageSlug: pageSlug!,
},
pause: pageSlug === null,
pause: !pageSlug,
});

const preferredTitle = seoPageData?.seoPage?.title ?? null;
const preferredDescription = seoPageData?.seoPage?.metaDescription ?? null;
const preferredCanonicalUrl = seoPageData?.seoPage?.canonicalUrl ?? null;
const preferredOgTitle = seoPageData?.seoPage?.ogTitle ?? null;
const preferredOgDescription = seoPageData?.seoPage?.ogDescription ?? null;
const preferredOgImageUrl = seoPageData?.seoPage?.ogImage?.sizes[0]?.url ?? null;
const preferredTitle = seoPageData?.seoPage?.title;
const preferredDescription = seoPageData?.seoPage?.metaDescription;
const preferredCanonicalUrl = seoPageData?.seoPage?.canonicalUrl;
const preferredOgTitle = seoPageData?.seoPage?.ogTitle;
const preferredOgDescription = seoPageData?.seoPage?.ogDescription;
const preferredOgImageUrl = seoPageData?.seoPage?.ogImage?.sizes[0]?.url;

const fallbackTitle = settingsData?.settings?.seo.title ?? null;
const fallbackDescription = settingsData?.settings?.seo.metaDescription ?? null;
const fallbackTitleSuffix = settingsData?.settings?.seo.titleAddOn ?? null;
const fallbackTitle = settingsData?.settings?.seo.title;
const fallbackDescription = settingsData?.settings?.seo.metaDescription;
const fallbackTitleSuffix = settingsData?.settings?.seo.titleAddOn;

const canonicalUrl = useMemo(() => {
return preferredCanonicalUrl ?? generateCanonicalUrl(router, url);
}, [router, url, preferredCanonicalUrl]);
const canonicalUrl = preferredCanonicalUrl || generateCanonicalUrl(router, url, canonicalQueryParams);

return {
title: preferredTitle ?? defaultTitle ?? fallbackTitle,
Expand Down
1 change: 1 addition & 0 deletions storefront/pages/articles/[articleSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const ArticleDetailPage: NextPage = () => {
title={article?.seoTitle}
description={article?.seoMetaDescription}
breadcrumbs={article?.breadcrumb}
canonicalQueryParams={[]}
>
{!!article && !fetching ? <ArticleDetailContent article={article} /> : <ArticlePageSkeleton />}
</CommonLayout>
Expand Down
1 change: 1 addition & 0 deletions storefront/pages/blogArticles/[blogArticleSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const BlogArticleDetailPage: NextPage = () => {
description={blogArticleData?.blogArticle?.seoMetaDescription}
breadcrumbs={blogArticleData?.blogArticle?.breadcrumb}
breadcrumbsType="blogCategory"
canonicalQueryParams={[]}
>
{!!blogArticleData?.blogArticle && !fetching ? (
<BlogArticleDetailContent blogArticle={blogArticleData.blogArticle} />
Expand Down
1 change: 1 addition & 0 deletions storefront/pages/products/[productSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const ProductDetailPage: NextPage = () => {
description={product?.seoMetaDescription}
breadcrumbs={product?.breadcrumb}
breadcrumbsType="category"
canonicalQueryParams={[]}
>
{fetching && <ProductDetailPageSkeleton />}

Expand Down
6 changes: 5 additions & 1 deletion storefront/pages/stores/[storeSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ const StoreDetailPage: NextPage = () => {
useGtmPageViewEvent(pageViewEvent, fetching);

return (
<CommonLayout title={storeDetailData?.store?.storeName} breadcrumbs={storeDetailData?.store?.breadcrumb}>
<CommonLayout
title={storeDetailData?.store?.storeName}
breadcrumbs={storeDetailData?.store?.breadcrumb}
canonicalQueryParams={[]}
>
{!!storeDetailData?.store && !fetching ? (
<StoreDetailContent store={storeDetailData.store} />
) : (
Expand Down

0 comments on commit a1dc0f4

Please sign in to comment.