Skip to content

Commit

Permalink
feat(seo): cleanup metadata fetching for layout and pages
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiazom committed Oct 11, 2024
1 parent c871ef9 commit 52443d1
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 158 deletions.
92 changes: 23 additions & 69 deletions src/app/(main)/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import { Metadata } from "next";

import CustomErrorMessage from "src/blog/components/customErrorMessage/CustomErrorMessage";
import InformationSection from "src/blog/components/informationSection/InformationSection";
import { homeLink } from "src/blog/components/utils/linkTypes";
import { getDraftModeInfo } from "src/utils/draftmode";
import SectionRenderer from "src/utils/renderSection";
import { fetchSeoData, generateMetadataFromSeo } from "src/utils/seo";
import { client } from "studio/lib/client";
import { generateMetadataFromSeo } from "src/utils/seo";
import { LinkType } from "studio/lib/interfaces/navigation";
import { PageBuilder } from "studio/lib/interfaces/pages";
import { LanguageObject } from "studio/lib/interfaces/supportedLanguages";
import { PAGE_QUERY, PAGE_SEO_QUERY } from "studio/lib/queries/pages";
import {
LANDING_PAGE_REF_QUERY,
LANGUAGES_QUERY,
} from "studio/lib/queries/siteSettings";
import { LANDING_PAGE_QUERY } from "studio/lib/queries/siteSettings";
import { loadStudioQuery } from "studio/lib/store";

export async function generateMetadata(): Promise<Metadata> {
const { data: landingId } = await loadStudioQuery<string>(
LANDING_PAGE_REF_QUERY,
const { data: landingPage } = await loadStudioQuery<PageBuilder | null>(
LANDING_PAGE_QUERY,
);
const seo = await fetchSeoData(PAGE_SEO_QUERY, { id: landingId });
return generateMetadataFromSeo(seo);
return generateMetadataFromSeo(landingPage?.seo ?? null);
}

const navigationManagerLink = {
Expand All @@ -33,44 +24,20 @@ const navigationManagerLink = {
internalLink: { _ref: "studio/structure/navigationManager" },
};

const pagesLink = {
_key: "go-to-pages",
_type: "link",
linkTitle: "Go to Pages Manager",
linkType: LinkType.Internal,
internalLink: { _ref: "studio/structure/pages" },
};

type Props = {
params: { lang: string; slug: string };
};

const Page404 = (
<CustomErrorMessage
title="404 — Something went wrong"
body="The page you are looking for does not exist. There may be an error in the URL, or the page may have been moved or deleted."
link={homeLink}
/>
);

const Home = async ({ params }: Props) => {
const { perspective, isDraftMode } = getDraftModeInfo();

const language = (
await client.fetch<LanguageObject[] | null>(LANGUAGES_QUERY)
)?.find((l) => l.id === params.lang);

if (language === undefined) {
return Page404;
}

const { data: landingId } = await loadStudioQuery<string>(
LANDING_PAGE_REF_QUERY,
{},
const initialLandingPage = await loadStudioQuery<PageBuilder | null>(
LANDING_PAGE_QUERY,
{ language: params.lang },
{ perspective },
);

if (!landingId) {
if (initialLandingPage.data === null) {
return (
<InformationSection
title="Welcome! Velkommen! Välkommen!"
Expand All @@ -82,34 +49,21 @@ const Home = async ({ params }: Props) => {
);
}

const initialLandingPage = await loadStudioQuery<PageBuilder>(
PAGE_QUERY,
{ id: landingId, language: params.lang },
{ perspective },
);
const initialData = {
...initialLandingPage,
data: initialLandingPage.data,
};

if (!initialLandingPage.data) {
return (
<InformationSection
title="Landing Page is Missing Content"
body={`Your landing page is set, but it looks like there’s no content yet.\n Visit the Studio to start adding content and make your landing page come to life!`}
link={pagesLink}
/>
);
}

return initialLandingPage.data.sections.map((section, index) => {
return (
<SectionRenderer
key={section._key}
section={section}
isDraftMode={isDraftMode}
initialData={initialLandingPage}
isLandingPage={true}
sectionIndex={index}
/>
);
});
return initialLandingPage.data.sections.map((section, index) => (
<SectionRenderer
key={section._key}
section={section}
isDraftMode={isDraftMode}
initialData={initialData}
isLandingPage={true}
sectionIndex={index}
/>
));
};

export default Home;
53 changes: 3 additions & 50 deletions src/utils/seo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { toPlainText } from "@portabletext/toolkit";
import type { QueryParams } from "@sanity/client";
import { Metadata } from "next";
import { PortableTextBlock } from "sanity";

import { urlFor } from "studio/lib/image";
import { BrandAssets } from "studio/lib/interfaces/brandAssets";
Expand All @@ -21,55 +18,11 @@ export type SeoData = {
keywords?: string;
};

type PostSeoData = {
title: string;
description: PortableTextBlock[];
imageUrl: string;
keywords: string;
};

export const OPEN_GRAPH_IMAGE_DIMENSIONS = {
width: 1200,
height: 630,
};

export async function fetchSeoData(
query: string,
variables?: QueryParams | undefined,
): Promise<SeoData | null> {
try {
const { data } = await loadStudioQuery<SeoData>(query, variables);
return data;
} catch (error) {
console.error("Error loading SEO data:", error);
return null;
}
}

export async function fetchPostSeoData(
query: string,
variables?: QueryParams | undefined,
): Promise<SeoData | null> {
try {
const { data } = await loadStudioQuery<PostSeoData>(query, variables);
if (data && data.description) {
const plainTextDescription = toPlainText(data.description);

return {
title: data.title,
description: plainTextDescription,
imageUrl: data.imageUrl,
keywords: data.keywords,
};
}

return null;
} catch (error) {
console.error("Error loading SEO data:", error);
return null;
}
}

export async function generateMetadataFromSeo(
seo: SeoData | null,
): Promise<Metadata> {
Expand All @@ -85,10 +38,10 @@ export async function generateMetadataFromSeo(

const title =
seo?.title ||
defaultSeo?.seo?.seoTitle ||
defaultSeo?.seo?.title ||
companyInfo?.companyName ||
"Variant";
const description = seo?.description || defaultSeo?.seo?.seoDescription;
const description = seo?.description || defaultSeo?.seo?.description;
const keywords = seo?.keywords || "";

const favicon = brandAssets?.favicon;
Expand All @@ -102,7 +55,7 @@ export async function generateMetadataFromSeo(
title: title,
...(description ? { description: description } : {}),
})}`;
const sanityImageUrl = seo?.imageUrl || defaultSeo?.seo?.seoImageUrl;
const sanityImageUrl = seo?.imageUrl || defaultSeo?.seo?.imageUrl;
const sanityImageParams = `?${new URLSearchParams({
w: OPEN_GRAPH_IMAGE_DIMENSIONS.width.toString(),
h: OPEN_GRAPH_IMAGE_DIMENSIONS.height.toString(),
Expand Down
3 changes: 3 additions & 0 deletions studio/lib/interfaces/pages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { PortableTextBlock } from "sanity";

import { SeoData } from "src/utils/seo";

import { Slug } from "./global";
import { IImage, ImageExtendedProps } from "./media";
import { ILink } from "./navigation";
Expand Down Expand Up @@ -103,6 +105,7 @@ export interface PageBuilder {
page: string;
sections: Section[];
slug: Slug;
seo: SeoData;
}

export interface SEO {
Expand Down
9 changes: 2 additions & 7 deletions studio/lib/interfaces/seo.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
export interface SeoObject {
seoTitle?: string;
seoDescription?: string;
seoKeywords?: string;
seoImageUrl?: string;
}
import { SeoData } from "src/utils/seo";

export type DefaultSeo = {
_id: string;
_type: "seoFallback";
_createdAt: string;
_updatedAt: string;
_rev: string;
seo?: SeoObject;
seo?: SeoData;
};
37 changes: 12 additions & 25 deletions studio/lib/queries/pages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { groq } from "next-sanity";

import { LANGUAGE_FIELD_FRAGMENT, TRANSLATED_LINK_FRAGMENT } from "./i18n";
import { translatedFieldFragment } from "./utils/i18n";

const SECTIONS_FRAGMENT = groq`
sections[]{
Expand Down Expand Up @@ -36,10 +37,20 @@ const SECTIONS_FRAGMENT = groq`
}
`;

export const SEO_FRAGMENT = groq`
"seo": ${translatedFieldFragment("seo")} {
"title": seoTitle,
"description": seoDescription,
"imageUrl": seoImage.asset->url,
"keywords": seoKeywords
},
`;

export const PAGE_FRAGMENT = groq`
...,
${LANGUAGE_FIELD_FRAGMENT},
${SECTIONS_FRAGMENT}
${SECTIONS_FRAGMENT},
${SEO_FRAGMENT}
`;

export const PAGE_QUERY = groq`
Expand All @@ -48,28 +59,12 @@ export const PAGE_QUERY = groq`
}
`;

export const PAGE_SEO_QUERY = groq`
*[_type == "pageBuilder" && _id == $id][0]{
"title": seo.seoTitle,
"description": seo.seoDescription,
"imageUrl": seo.seoImage.asset->url
}
`;

export const PAGE_BY_SLUG_QUERY = groq`
*[_type == "pageBuilder" && slug.current == $slug][0]{
${PAGE_FRAGMENT}
}
`;

export const SEO_SLUG_QUERY = groq`
*[defined(seo) && slug.current == $slug][0]{
"title": seo.seoTitle,
"description": seo.seoDescription,
"imageUrl": seo.seoImage.asset->url
}
`;

export const BLOG_PAGE_QUERY = groq`
*[_type == "blog" && slug.current == $slug][0]
`;
Expand All @@ -90,14 +85,6 @@ export const POST_SLUG_QUERY = groq`
*[_type == "blogPosts" && slug.current == $id][0]
`;

export const SEO_POST_SLUG_QUERY = groq`
*[_type == "blogPosts" && slug.current == $id][0]{
"title": basicTitle,
"description": richText,
"imageUrl": image.asset->url
}
`;

export const MORE_POST_PREVIEW = groq`
*[_type == "blogPosts"] | order(_createdAt desc)[0..2]
`;
10 changes: 3 additions & 7 deletions studio/lib/queries/siteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
TRANSLATED_LINK_FRAGMENT,
TRANSLATED_SLUG_VALUE_FRAGMENT,
} from "./i18n";
import { PAGE_FRAGMENT } from "./pages";
import { PAGE_FRAGMENT, SEO_FRAGMENT } from "./pages";

//Brand Assets
export const BRAND_ASSETS_QUERY = groq`
Expand Down Expand Up @@ -63,11 +63,7 @@ export const DEFAULT_LANGUAGE_QUERY = groq`
//Default SEO
export const DEFAULT_SEO_QUERY = groq`
*[_type == "seoFallback"][0]{
seo {
seoTitle,
seoDescription,
seoKeywords,
"seoImageUrl": seoImage.asset->url
}
...,
${SEO_FRAGMENT}
}
`;

0 comments on commit 52443d1

Please sign in to comment.