From f25b6f83660e4ec793f25ea8bcdbcc21bee246b5 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:36:37 -0700 Subject: [PATCH 01/18] revert: remove Storyblok CMS integration --- .vscode/settings.json | 2 +- next.config.js | 1 - package.json | 4 - src/app/StoryblokRenderer.tsx | 38 ---- src/app/[...slug]/page.tsx | 53 ----- src/app/[slug]/page.tsx | 110 +++++++++ src/app/about/page.tsx | 95 ++++++++ src/app/blog/[slug]/page.tsx | 3 +- src/app/blog/page.tsx | 5 +- src/app/blog/preview/[id]/page.tsx | 3 +- src/app/blog/series/[slug]/page.tsx | 3 +- src/app/blog/tag/[slug]/page.tsx | 3 +- src/app/compare/[slug]/page.tsx | 139 +++++++++++ src/app/compare/layout.tsx | 9 + src/app/compare/page.tsx | 5 + src/app/frequently-asked-questions/page.tsx | 40 ++++ src/app/layout.tsx | 105 +++++---- src/app/metadata.ts | 58 ----- src/app/page.tsx | 96 ++++---- src/app/podcast/[slug]/page.tsx | 3 +- src/app/podcast/page.tsx | 5 +- src/app/pricing/page.tsx | 49 ++++ src/assets/compare/aws-config.svg | 1 + src/assets/compare/cartography.svg | 1 + .../compare/google-cloud-asset-inventory.svg | 1 + src/assets/compare/steampipe.svg | 1 + src/assets/compare/wiz.svg | 1 + src/assets/customers/bloomreach.svg | 1 + src/assets/customers/despegar.svg | 1 + src/assets/customers/electronic-arts.svg | 1 + src/assets/customers/kelloggs.svg | 1 + src/assets/customers/mars.svg | 1 + src/assets/customers/payplug.svg | 1 + src/assets/diagrams/cspm-mobile.svg | 1 + src/assets/diagrams/cspm.svg | 1 + src/assets/screenshots/dashboard.png | Bin 0 -> 287863 bytes src/assets/team/lars.jpg | Bin 0 -> 444905 bytes src/assets/team/lukas.jpg | Bin 0 -> 689386 bytes src/assets/team/matthias.jpg | Bin 0 -> 401852 bytes src/components/analytics/PosthogPageView.tsx | 2 +- src/components/blog/BlogNewsletterForm.tsx | 2 +- src/components/layout/Header.tsx | 75 +++--- src/components/storyblok/About.js | 24 -- src/components/storyblok/Compare.js | 45 ---- src/components/storyblok/Customers.js | 41 ---- src/components/storyblok/Dashboard.js | 9 - src/components/storyblok/Div.js | 13 -- src/components/storyblok/Header1.js | 13 -- src/components/storyblok/HeadlineTextBlock.js | 32 --- .../storyblok/HeadlineTextBlockMinimal.js | 29 --- src/components/storyblok/Hero.js | 44 ---- src/components/storyblok/Motivation.js | 104 --------- src/components/storyblok/Page.js | 15 -- src/components/storyblok/Picture.js | 19 -- src/components/storyblok/RichText.js | 11 - src/components/storyblok/Section.js | 20 -- src/components/storyblok/Span.js | 13 -- .../storyblok/StoryblokBridgeLoader.js | 27 --- src/components/storyblok/StoryblokImage.js | 53 ----- src/components/storyblok/Team.js | 73 ------ src/components/storyblok/Teaser.js | 11 - src/components/storyblok/Testimonials.js | 58 ----- src/components/storyblok/Text.js | 5 - src/components/storyblok/faq/FAQ.js | 57 ----- src/components/storyblok/pricing/Pricing.js | 153 ------------- .../pricing/Pricing_Additional_Seats.js | 27 --- .../storyblok/pricing/Pricing_Custom_Plans.js | 26 --- .../storyblok/pricing/Pricing_Span.js | 10 - src/lib/storyblok.ts | 68 ------ src/middleware.ts | 15 +- src/providers/StoryblokProvider.tsx | 22 -- src/styles/components.scss | 100 -------- src/styles/cookiebot.scss | 102 --------- src/styles/{globals.scss => globals.css} | 15 +- src/styles/main.scss | 10 - src/utils/richTextRenderer.js | 5 - src/utils/sanitizeClass.ts | 8 - storyblok.js | 50 ---- yarn.lock | 215 +++++++----------- 79 files changed, 718 insertions(+), 1745 deletions(-) delete mode 100644 src/app/StoryblokRenderer.tsx delete mode 100644 src/app/[...slug]/page.tsx create mode 100644 src/app/[slug]/page.tsx create mode 100644 src/app/about/page.tsx create mode 100644 src/app/compare/[slug]/page.tsx create mode 100644 src/app/compare/layout.tsx create mode 100644 src/app/compare/page.tsx create mode 100644 src/app/frequently-asked-questions/page.tsx delete mode 100644 src/app/metadata.ts create mode 100644 src/app/pricing/page.tsx create mode 100644 src/assets/compare/aws-config.svg create mode 100644 src/assets/compare/cartography.svg create mode 100644 src/assets/compare/google-cloud-asset-inventory.svg create mode 100644 src/assets/compare/steampipe.svg create mode 100644 src/assets/compare/wiz.svg create mode 100644 src/assets/customers/bloomreach.svg create mode 100644 src/assets/customers/despegar.svg create mode 100644 src/assets/customers/electronic-arts.svg create mode 100644 src/assets/customers/kelloggs.svg create mode 100644 src/assets/customers/mars.svg create mode 100644 src/assets/customers/payplug.svg create mode 100644 src/assets/diagrams/cspm-mobile.svg create mode 100644 src/assets/diagrams/cspm.svg create mode 100644 src/assets/screenshots/dashboard.png create mode 100644 src/assets/team/lars.jpg create mode 100644 src/assets/team/lukas.jpg create mode 100644 src/assets/team/matthias.jpg delete mode 100644 src/components/storyblok/About.js delete mode 100644 src/components/storyblok/Compare.js delete mode 100644 src/components/storyblok/Customers.js delete mode 100644 src/components/storyblok/Dashboard.js delete mode 100644 src/components/storyblok/Div.js delete mode 100644 src/components/storyblok/Header1.js delete mode 100644 src/components/storyblok/HeadlineTextBlock.js delete mode 100644 src/components/storyblok/HeadlineTextBlockMinimal.js delete mode 100644 src/components/storyblok/Hero.js delete mode 100644 src/components/storyblok/Motivation.js delete mode 100644 src/components/storyblok/Page.js delete mode 100644 src/components/storyblok/Picture.js delete mode 100644 src/components/storyblok/RichText.js delete mode 100644 src/components/storyblok/Section.js delete mode 100644 src/components/storyblok/Span.js delete mode 100644 src/components/storyblok/StoryblokBridgeLoader.js delete mode 100644 src/components/storyblok/StoryblokImage.js delete mode 100644 src/components/storyblok/Team.js delete mode 100644 src/components/storyblok/Teaser.js delete mode 100644 src/components/storyblok/Testimonials.js delete mode 100644 src/components/storyblok/Text.js delete mode 100644 src/components/storyblok/faq/FAQ.js delete mode 100644 src/components/storyblok/pricing/Pricing.js delete mode 100644 src/components/storyblok/pricing/Pricing_Additional_Seats.js delete mode 100644 src/components/storyblok/pricing/Pricing_Custom_Plans.js delete mode 100644 src/components/storyblok/pricing/Pricing_Span.js delete mode 100644 src/lib/storyblok.ts delete mode 100644 src/providers/StoryblokProvider.tsx delete mode 100644 src/styles/components.scss delete mode 100644 src/styles/cookiebot.scss rename src/styles/{globals.scss => globals.css} (94%) delete mode 100644 src/styles/main.scss delete mode 100644 src/utils/richTextRenderer.js delete mode 100644 src/utils/sanitizeClass.ts delete mode 100644 storyblok.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e500d4..800cb08 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,7 +13,7 @@ "typescriptreact" ], "files.associations": { - "globals.scss": "tailwindcss" + "*.css": "tailwindcss" }, "tailwindCSS.classAttributes": [ "class", diff --git a/next.config.js b/next.config.js index 7cf5eb7..9025774 100644 --- a/next.config.js +++ b/next.config.js @@ -18,7 +18,6 @@ module.exports = withPlausibleProxy()({ { protocol: 'https', hostname: 'cdn.hashnode.com' }, { protocol: 'https', hostname: 'i.scdn.co' }, { protocol: 'https', hostname: 'img.transistor.fm' }, - { protocol: 'https', hostname: 'a.storyblok.com' }, ], }, diff --git a/package.json b/package.json index 577f365..2e9201c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@headlessui/react": "2.1.2", "@hookform/resolvers": "3.9.0", "@next/third-parties": "14.2.5", - "@storyblok/react": "^3.0.10", "clsx": "2.1.1", "entities": "5.0.0", "feed": "4.2.2", @@ -46,11 +45,8 @@ "remark": "15.0.1", "remark-gfm": "4.0.0", "remark-smartypants": "3.0.2", - "sass": "^1.77.8", "server-only": "0.0.1", "sharp": "0.33.4", - "storyblok-react": "^0.1.2", - "storyblok-rich-text-react-renderer": "^2.9.2", "tailwind-merge": "2.4.0", "zod": "3.23.8", "zod-form-data": "2.0.2" diff --git a/src/app/StoryblokRenderer.tsx b/src/app/StoryblokRenderer.tsx deleted file mode 100644 index 50115ee..0000000 --- a/src/app/StoryblokRenderer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client'; - -import { - ISbStoryData, - StoryblokComponent, - useStoryblokState, -} from '@storyblok/react'; -import React from 'react'; - -interface Blok { - _uid: string; - component: string; - [key: string]: unknown; -} - -// Define the content type used in ISbStoryData -interface StoryContent { - body: Blok[]; -} - -interface StoryblokRendererProps { - story: ISbStoryData; -} - -const StoryblokRenderer: React.FC = ({ story }) => { - // Enable live updates in the Storyblok Visual Editor - const liveStory = useStoryblokState(story); - - return ( -
- {liveStory?.content?.body.map((blok) => ( - - ))} -
- ); -}; - -export default StoryblokRenderer; diff --git a/src/app/[...slug]/page.tsx b/src/app/[...slug]/page.tsx deleted file mode 100644 index b55ca88..0000000 --- a/src/app/[...slug]/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { getStoryblokApi, ISbStoriesParams } from '@storyblok/react'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; - -import StoryblokRenderer from '@/app/StoryblokRenderer'; -import { generateMetadataFromStory } from '@/lib/storyblok'; - -async function fetchData( - slug: string, - version: 'published' | 'draft' | undefined, -) { - const cacheVersion = Math.floor(Date.now() / 1000); - const sbParams: ISbStoriesParams = { - version: version, - cv: cacheVersion, // Force bypass cache - }; - const storyblokApi = getStoryblokApi(); - - if (!storyblokApi) { - throw new Error('Storyblok API is not initialized'); - } - - return await storyblokApi.get(`cdn/stories/${slug}`, sbParams); -} - -export async function generateMetadata({ - params, -}: { - params: { slug: string[] }; -}): Promise { - const story = await fetchData(params.slug.join('/'), 'published'); - - return generateMetadataFromStory(story, false); -} - -export default async function Page({ - params, - searchParams, -}: { - params: { slug: string[] }; - searchParams: { _storyblok?: string }; -}) { - const slugPath = params.slug.join('/'); - let data; - try { - const version = searchParams._storyblok ? 'draft' : 'published'; - const response = await fetchData(slugPath, version); - data = response.data; - } catch (error) { - notFound(); - } - return ; -} diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx new file mode 100644 index 0000000..fb8d8c0 --- /dev/null +++ b/src/app/[slug]/page.tsx @@ -0,0 +1,110 @@ +import type { Metadata } from 'next'; +import { notFound, permanentRedirect } from 'next/navigation'; + +import { metadata as rootMetadata } from '@/app/layout'; +import { metadata as notFoundMetadata } from '@/app/not-found'; +import HashnodePageView from '@/components/analytics/HashnodePageView'; +import MarkdownContent from '@/components/common/MarkdownContent'; +import { siteConfig } from '@/constants/config'; +import { isProd } from '@/constants/env'; +import { + getAllStaticPageSlugs, + getPublicationId, + getStaticPage, +} from '@/lib/hashnode'; +import { openGraph } from '@/utils/og'; + +export const revalidate = 300; + +export async function generateStaticParams() { + const slugs = await getAllStaticPageSlugs(); + + return slugs + .filter((slug) => !slug.startsWith('fix-vs-')) + .map((slug) => ({ + slug, + })); +} + +export async function generateMetadata({ + params, +}: { + params: { slug: string }; +}): Promise { + const staticPage = await getStaticPage(params.slug); + + if (!staticPage) { + return notFoundMetadata; + } + + const url = `${siteConfig.url}/${staticPage.slug}`; + const title = staticPage.title; + const description = staticPage.seo?.description ?? undefined; + const ogImage = openGraph({ title, description }); + + return { + title, + description, + alternates: { + ...rootMetadata.alternates, + canonical: url, + }, + openGraph: { + ...rootMetadata.openGraph, + url, + title, + description, + images: [ogImage], + }, + twitter: { + ...rootMetadata.twitter, + title: `${title} | ${siteConfig.title}`, + description, + images: [ogImage], + }, + ...(staticPage.hidden ? { robots: notFoundMetadata.robots } : {}), + }; +} + +export default async function StaticPage({ + params, +}: { + params: { slug: string }; +}) { + if (params.slug.startsWith('fix-vs-')) { + permanentRedirect(`/compare/${params.slug}`); + } + + const publicationIdData = getPublicationId(); + const staticPageData = getStaticPage(params.slug); + + const [publicationId, staticPage] = await Promise.all([ + publicationIdData, + staticPageData, + ]); + + if (!staticPage) { + notFound(); + } + + return ( + <> +
+
+

+ {staticPage.title} +

+ + {staticPage.content.markdown} + +
+
+ {isProd && publicationId ? ( + + ) : null} + + ); +} diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..c1f623f --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,95 @@ +import { Metadata } from 'next'; +import { notFound } from 'next/navigation'; + +import { metadata as rootMetadata } from '@/app/layout'; +import { metadata as notFoundMetadata } from '@/app/not-found'; +import HashnodePageView from '@/components/analytics/HashnodePageView'; +import MarkdownContent from '@/components/common/MarkdownContent'; +import Faq from '@/components/sections/Faq'; +import Team from '@/components/sections/Team'; +import { siteConfig } from '@/constants/config'; +import { isProd } from '@/constants/env'; +import { getPublicationId, getStaticPage } from '@/lib/hashnode'; +import { openGraph } from '@/utils/og'; + +export const revalidate = 300; + +export async function generateMetadata(): Promise { + const staticPage = await getStaticPage('about'); + + if (!staticPage) { + return notFoundMetadata; + } + + const url = `${siteConfig.url}/${staticPage.slug}`; + const title = staticPage.title; + const description = staticPage.seo?.description ?? undefined; + const ogImage = openGraph({ title, description }); + + return { + title, + description, + alternates: { + ...rootMetadata.alternates, + canonical: url, + }, + openGraph: { + ...rootMetadata.openGraph, + url, + title, + description, + images: [ogImage], + }, + twitter: { + ...rootMetadata.twitter, + title: `${staticPage.title} | ${siteConfig.title}`, + description, + images: [ogImage], + }, + ...(staticPage.hidden ? { robots: notFoundMetadata.robots } : {}), + }; +} + +export default async function AboutPage() { + const publicationIdData = getPublicationId(); + const staticPageData = getStaticPage('about'); + + const [publicationId, staticPage] = await Promise.all([ + publicationIdData, + staticPageData, + ]); + + if (!staticPage) { + notFound(); + } + + return ( + <> +
+
+

+ {staticPage.title} +

+

+ We don’t have a{' '} + + silver bullet for cloud security + + . +

+ + {staticPage.content.markdown} + +
+
+ + + {isProd && publicationId ? ( + + ) : null} + + ); +} diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index b81ec00..e8e1b22 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { notFound, permanentRedirect } from 'next/navigation'; +import { metadata as rootMetadata } from '@/app/layout'; import { metadata as notFoundMetadata } from '@/app/not-found'; import BlogPost from '@/components/blog/BlogPost'; import { siteConfig } from '@/constants/config'; @@ -12,8 +13,6 @@ import { } from '@/lib/hashnode'; import { openGraph } from '@/utils/og'; -import { metadata as rootMetadata } from '../../metadata'; - export async function generateStaticParams() { const slugs = await getAllPostSlugs(); diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index f151248..fbeabbe 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import { metadata as rootMetadata } from '@/app/layout'; import HashnodePageView from '@/components/analytics/HashnodePageView'; import BlogPostList from '@/components/blog/BlogPostList'; import { siteConfig } from '@/constants/config'; @@ -8,8 +9,6 @@ import { isProd } from '@/constants/env'; import { getPosts, getPublication } from '@/lib/hashnode'; import { openGraph } from '@/utils/og'; -import { metadata as rootMetadata } from '../metadata'; - const url = `${siteConfig.url}/blog`; export async function generateMetadata(): Promise { @@ -19,7 +18,7 @@ export async function generateMetadata(): Promise { return {}; } - const title = `${publication.title || `${siteConfig.title} Blog`} | ${siteConfig.title}`; + const title = publication.title || `${siteConfig.title} Blog`; const description = publication.about?.text; const ogImage = openGraph({ title, diff --git a/src/app/blog/preview/[id]/page.tsx b/src/app/blog/preview/[id]/page.tsx index 4cb8bd4..a1ff15d 100644 --- a/src/app/blog/preview/[id]/page.tsx +++ b/src/app/blog/preview/[id]/page.tsx @@ -1,13 +1,12 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import { metadata as rootMetadata } from '@/app/layout'; import BlogDraft from '@/components/blog/BlogDraft'; import { siteConfig } from '@/constants/config'; import { getDraft, getPublication } from '@/lib/hashnode'; import { openGraph } from '@/utils/og'; -import { metadata as rootMetadata } from '../../../metadata'; - export const revalidate = 0; export async function generateMetadata({ diff --git a/src/app/blog/series/[slug]/page.tsx b/src/app/blog/series/[slug]/page.tsx index 58c5b40..73f88b1 100644 --- a/src/app/blog/series/[slug]/page.tsx +++ b/src/app/blog/series/[slug]/page.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import { metadata as rootMetadata } from '@/app/layout'; import HashnodePageView from '@/components/analytics/HashnodePageView'; import BlogPostList from '@/components/blog/BlogPostList'; import { siteConfig } from '@/constants/config'; @@ -13,8 +14,6 @@ import { } from '@/lib/hashnode'; import { openGraph } from '@/utils/og'; -import { metadata as rootMetadata } from '../../../metadata'; - export async function generateStaticParams() { const slugs = await getAllSeriesSlugs(); diff --git a/src/app/blog/tag/[slug]/page.tsx b/src/app/blog/tag/[slug]/page.tsx index c3b3e82..50deafe 100644 --- a/src/app/blog/tag/[slug]/page.tsx +++ b/src/app/blog/tag/[slug]/page.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import { metadata as rootMetadata } from '@/app/layout'; import HashnodePageView from '@/components/analytics/HashnodePageView'; import BlogPostList from '@/components/blog/BlogPostList'; import { siteConfig } from '@/constants/config'; @@ -13,8 +14,6 @@ import { } from '@/lib/hashnode'; import { openGraph } from '@/utils/og'; -import { metadata as rootMetadata } from '../../../metadata'; - export async function generateStaticParams() { const slugs = await getAllTagSlugs(); diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx new file mode 100644 index 0000000..79c0287 --- /dev/null +++ b/src/app/compare/[slug]/page.tsx @@ -0,0 +1,139 @@ +import type { Metadata } from 'next'; +import { notFound, permanentRedirect } from 'next/navigation'; + +import { metadata as rootMetadata } from '@/app/layout'; +import { metadata as notFoundMetadata } from '@/app/not-found'; +import FixLogo from '@/assets/logo.svg'; +import HashnodePageView from '@/components/analytics/HashnodePageView'; +import MarkdownContent from '@/components/common/MarkdownContent'; +import CompetitorLogo, { hasLogo } from '@/components/compare/CompetitorLogo'; +import Customers from '@/components/sections/Customers'; +import Faq from '@/components/sections/Faq'; +import { siteConfig } from '@/constants/config'; +import { isProd } from '@/constants/env'; +import { + getAllStaticPageSlugs, + getPublicationId, + getStaticPage, +} from '@/lib/hashnode'; +import { openGraph } from '@/utils/og'; + +export const revalidate = 300; + +export async function generateStaticParams() { + const slugs = await getAllStaticPageSlugs(); + + return slugs + .filter((slug) => slug.startsWith('fix-vs-')) + .map((slug) => ({ + slug, + })); +} + +export async function generateMetadata({ + params, +}: { + params: { slug: string }; +}): Promise { + const staticPage = await getStaticPage(params.slug); + + if (!staticPage) { + return notFoundMetadata; + } + + const url = `${siteConfig.url}/compare/${staticPage.slug}`; + const title = `Fix Security vs. ${staticPage.title}`; + const description = staticPage.seo?.description ?? undefined; + const ogImage = openGraph({ title, description }); + + return { + title, + description, + alternates: { + ...rootMetadata.alternates, + canonical: url, + }, + openGraph: { + ...rootMetadata.openGraph, + url, + title, + description, + images: [ogImage], + }, + twitter: { + ...rootMetadata.twitter, + title: `${title} | ${siteConfig.title}`, + description, + images: [ogImage], + }, + ...(staticPage.hidden ? { robots: notFoundMetadata.robots } : {}), + }; +} + +export default async function ComparisonPage({ + params, +}: { + params: { slug: string }; +}) { + if (!params.slug.startsWith('fix-vs-')) { + permanentRedirect(`/${params.slug}`); + } + + const publicationIdData = getPublicationId(); + const staticPageData = getStaticPage(params.slug); + + const [publicationId, staticPage] = await Promise.all([ + publicationIdData, + staticPageData, + ]); + + if (!staticPage) { + notFound(); + } + + const title = `Fix Security vs. ${staticPage.title}`; + const subtitle = `Why engineers choose Fix Security over ${staticPage.title}`; + const competitorSlug = params.slug.replace('fix-vs-', ''); + + return ( + <> +
+
+ {hasLogo(competitorSlug) ? ( + <> +

{title}

+ + + ) : ( +

+ {title} +

+ )} +

+ {subtitle} +

+ + {staticPage.content.markdown} + +
+
+ + + {isProd && publicationId ? ( + + ) : null} + + ); +} diff --git a/src/app/compare/layout.tsx b/src/app/compare/layout.tsx new file mode 100644 index 0000000..7a3ddef --- /dev/null +++ b/src/app/compare/layout.tsx @@ -0,0 +1,9 @@ +export const revalidate = 300; + +export default function CompareLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/compare/page.tsx b/src/app/compare/page.tsx new file mode 100644 index 0000000..40cc3e8 --- /dev/null +++ b/src/app/compare/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default async function ComparePage() { + redirect('/'); +} diff --git a/src/app/frequently-asked-questions/page.tsx b/src/app/frequently-asked-questions/page.tsx new file mode 100644 index 0000000..25fcf53 --- /dev/null +++ b/src/app/frequently-asked-questions/page.tsx @@ -0,0 +1,40 @@ +import { Metadata } from 'next'; + +import { metadata as rootMetadata } from '@/app/layout'; +import Faq from '@/components/sections/Faq'; +import { siteConfig } from '@/constants/config'; +import { openGraph } from '@/utils/og'; + +const url = `${siteConfig.url}/frequently-asked-questions`; +const title = 'Frequently asked questions'; +const description = siteConfig.description; +const ogImage = openGraph({ + title, + description, +}); + +export const metadata: Metadata = { + title, + description, + alternates: { + ...rootMetadata.alternates, + canonical: url, + }, + openGraph: { + ...rootMetadata.openGraph, + url, + title, + description, + images: [ogImage], + }, + twitter: { + ...rootMetadata.twitter, + title: `${title} | ${siteConfig.title}`, + description, + images: [ogImage], + }, +}; + +export default async function FaqPage() { + return ; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c86e9ec..4dd0b68 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,31 +1,73 @@ -import { apiPlugin, storyblokInit } from '@storyblok/react'; -import { StoryblokBridgeLoader } from '@storyblok/react/rsc'; -import { Viewport } from 'next'; +import { Metadata, Viewport } from 'next'; import { headers } from 'next/headers'; -import Script from 'next/script'; import PlausibleProvider from 'next-plausible'; -import React, { Suspense } from 'react'; +import { Suspense } from 'react'; -import '@/styles/main.scss'; +import '@/styles/globals.css'; import { plusJakartaSans } from '@/app/fonts'; +import CookieConsent from '@/components/analytics/CookieConsent'; import PosthogPageView from '@/components/analytics/PosthogPageView'; import BlogNewsletterForm from '@/components/blog/BlogNewsletterForm'; import Footer from '@/components/layout/Footer'; import Header from '@/components/layout/Header'; +import { siteConfig } from '@/constants/config'; +import { isProd } from '@/constants/env'; import PosthogProvider from '@/providers/posthog'; -import StoryblokProvider from '@/providers/StoryblokProvider'; +import { openGraph } from '@/utils/og'; -import components from '../../storyblok'; +const url = siteConfig.url; +const title = siteConfig.title; +const description = siteConfig.description; +const ogImage = openGraph({ + title: siteConfig.tagline, + description, +}); -storyblokInit({ - accessToken: process.env.STORYBLOK_OAUTH_TOKEN, - use: [apiPlugin], - components, - apiOptions: { - cache: { type: 'none' }, +export const metadata: Metadata = { + title: { + default: `${title}: ${siteConfig.tagline}`, + template: `%s | ${title}`, }, -}); + description, + robots: isProd + ? { index: true, follow: true } + : { index: false, follow: false }, + icons: { + icon: '/favicon.ico', + shortcut: '/favicon-16x16.png', + apple: '/apple-touch-icon.png', + }, + manifest: '/site.webmanifest', + alternates: { + types: { + 'application/rss+xml': [ + { url: '/blog/rss.xml', title: 'Fix Security blog RSS feed' }, + ], + 'application/atom+xml': [ + { url: '/blog/atom.xml', title: 'Fix Security blog Atom feed' }, + ], + 'application/json': [ + { url: '/blog/feed.json', title: 'Fix Security blog JSON feed' }, + ], + }, + }, + openGraph: { + url, + title, + description, + siteName: title, + images: [ogImage], + type: 'website', + locale: 'en_US', + }, + twitter: { + card: 'summary_large_image', + title, + description, + images: [ogImage], + }, +}; export const viewport: Viewport = { themeColor: '#3d58d3', @@ -42,52 +84,23 @@ export default function RootLayout({ return ( -