diff --git a/apps/web/app/(marketing)/blog/[slug]/page.tsx b/apps/web/app/(marketing)/blog/[slug]/page.tsx
index 804f9b415..215f9e3e6 100644
--- a/apps/web/app/(marketing)/blog/[slug]/page.tsx
+++ b/apps/web/app/(marketing)/blog/[slug]/page.tsx
@@ -36,7 +36,7 @@ export async function generateMetadata({
title,
description,
type: 'article',
- publishedTime: publishedAt?.toDateString(),
+ publishedTime: publishedAt,
url: post.url,
images: image
? [
diff --git a/apps/web/app/(marketing)/blog/_components/post-header.tsx b/apps/web/app/(marketing)/blog/_components/post-header.tsx
index 7fbff4201..e9222228e 100644
--- a/apps/web/app/(marketing)/blog/_components/post-header.tsx
+++ b/apps/web/app/(marketing)/blog/_components/post-header.tsx
@@ -20,7 +20,7 @@ export const PostHeader: React.FC<{
-
+
diff --git a/apps/web/app/(marketing)/blog/_components/post-preview.tsx b/apps/web/app/(marketing)/blog/_components/post-preview.tsx
index 5ea463565..2641b4cf6 100644
--- a/apps/web/app/(marketing)/blog/_components/post-preview.tsx
+++ b/apps/web/app/(marketing)/blog/_components/post-preview.tsx
@@ -50,7 +50,7 @@ export function PostPreview({
diff --git a/apps/web/app/(marketing)/blog/page.tsx b/apps/web/app/(marketing)/blog/page.tsx
index 86df0eb59..18926d722 100644
--- a/apps/web/app/(marketing)/blog/page.tsx
+++ b/apps/web/app/(marketing)/blog/page.tsx
@@ -1,3 +1,5 @@
+import { unstable_cache as cache } from 'next/dist/server/web/spec-extension/unstable-cache';
+
import { createCmsClient } from '@kit/cms';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
@@ -18,22 +20,33 @@ export const generateMetadata = async () => {
};
};
+const getContentItems = cache(
+ async (language: string | undefined, limit: number, offset: number) => {
+ const client = await createCmsClient();
+
+ return client.getContentItems({
+ collection: 'posts',
+ limit,
+ offset,
+ language,
+ sortBy: 'publishedAt',
+ sortDirection: 'desc',
+ });
+ },
+);
+
async function BlogPage({ searchParams }: { searchParams: { page: string } }) {
const { t, resolvedLanguage: language } = await createI18nServerInstance();
- const cms = await createCmsClient();
const page = searchParams.page ? parseInt(searchParams.page) : 0;
const limit = 10;
const offset = page * limit;
- const { items: posts, total } = await cms.getContentItems({
- collection: 'posts',
+ const { total, items: posts } = await getContentItems(
+ language,
limit,
offset,
- language,
- sortBy: 'publishedAt',
- sortDirection: 'desc',
- });
+ );
return (
<>
diff --git a/apps/web/app/(marketing)/docs/[...slug]/page.tsx b/apps/web/app/(marketing)/docs/[...slug]/page.tsx
index 7b183c74e..b9b5600eb 100644
--- a/apps/web/app/(marketing)/docs/[...slug]/page.tsx
+++ b/apps/web/app/(marketing)/docs/[...slug]/page.tsx
@@ -1,5 +1,4 @@
-import { cache } from 'react';
-
+import { unstable_cache as cache } from 'next/cache';
import { notFound } from 'next/navigation';
import { ContentRenderer, createCmsClient } from '@kit/cms';
diff --git a/apps/web/app/(marketing)/docs/page.tsx b/apps/web/app/(marketing)/docs/page.tsx
index 902091dcd..6d571bb85 100644
--- a/apps/web/app/(marketing)/docs/page.tsx
+++ b/apps/web/app/(marketing)/docs/page.tsx
@@ -1,3 +1,5 @@
+import { unstable_cache as cache } from 'next/cache';
+
import { createCmsClient } from '@kit/cms';
import { PageBody } from '@kit/ui/page';
@@ -14,14 +16,18 @@ export const generateMetadata = async () => {
};
};
-async function DocsPage() {
+const getContentItems = cache(async (resolvedLanguage: string | undefined) => {
const client = await createCmsClient();
- const { t, resolvedLanguage } = await createI18nServerInstance();
- const { items } = await client.getContentItems({
+ return client.getContentItems({
collection: 'documentation',
language: resolvedLanguage,
});
+});
+
+async function DocsPage() {
+ const { t, resolvedLanguage } = await createI18nServerInstance();
+ const { items } = await getContentItems(resolvedLanguage);
// Filter out any docs that have a parentId, as these are children of other docs
const cards = items.filter((item) => !item.parentId);
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 2cd75f400..789c26526 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,32 +1,17 @@
-import { Urbanist as HeadingFont, Inter as SansFont } from 'next/font/google';
import Head from 'next/head';
-import { cookies, headers } from 'next/headers';
+import { cookies } from 'next/headers';
import { Toaster } from '@kit/ui/sonner';
import { cn } from '@kit/ui/utils';
+import { CsrfTokenMeta } from '~/components/csrf-token-meta';
import { RootProviders } from '~/components/root-providers';
-import appConfig from '~/config/app.config';
+import { heading, sans } from '~/lib/fonts';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
+import { rootMetadata } from '~/lib/root-metdata';
import '../styles/globals.css';
-const sans = SansFont({
- subsets: ['latin'],
- variable: '--font-sans',
- fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
- preload: true,
- weight: ['300', '400', '500', '600', '700'],
-});
-
-const heading = HeadingFont({
- subsets: ['latin'],
- variable: '--font-heading',
- fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
- preload: true,
- weight: ['500', '700'],
-});
-
export default async function RootLayout({
children,
}: {
@@ -34,9 +19,10 @@ export default async function RootLayout({
}) {
const { language } = await createI18nServerInstance();
const theme = getTheme();
+ const className = getClassName(theme);
return (
-
+
@@ -71,33 +57,4 @@ function getTheme() {
return cookies().get('theme')?.value;
}
-export const metadata = {
- title: appConfig.name,
- description: appConfig.description,
- metadataBase: new URL(appConfig.url),
- openGraph: {
- url: appConfig.url,
- siteName: appConfig.name,
- description: appConfig.description,
- },
- twitter: {
- card: 'summary_large_image',
- title: appConfig.title,
- description: appConfig.description,
- },
- icons: {
- icon: '/images/favicon/favicon.ico',
- shortcut: '/shortcut-icon.png',
- apple: '/images/favicon/apple-touch-icon.png',
- other: {
- rel: 'apple-touch-icon-precomposed',
- url: '/apple-touch-icon-precomposed.png',
- },
- },
-};
-
-function CsrfTokenMeta() {
- const csrf = headers().get('x-csrf-token') ?? '';
-
- return ;
-}
+export const metadata = rootMetadata;
diff --git a/apps/web/components/csrf-token-meta.tsx b/apps/web/components/csrf-token-meta.tsx
new file mode 100644
index 000000000..5e40cd37d
--- /dev/null
+++ b/apps/web/components/csrf-token-meta.tsx
@@ -0,0 +1,12 @@
+import { headers } from 'next/headers';
+
+/**
+ * @description This component is used to render the CSRF token as a meta tag.
+ * this tag can be retrieved for use in forms that require CSRF protection.
+ * @constructor
+ */
+export function CsrfTokenMeta() {
+ const csrf = headers().get('x-csrf-token') ?? '';
+
+ return ;
+}
diff --git a/apps/web/lib/fonts.ts b/apps/web/lib/fonts.ts
new file mode 100644
index 000000000..85572e07a
--- /dev/null
+++ b/apps/web/lib/fonts.ts
@@ -0,0 +1,30 @@
+import { Urbanist as HeadingFont, Inter as SansFont } from 'next/font/google';
+
+/**
+ * @sans
+ * @description Define here the sans font.
+ * By default, it uses the Inter font from Google Fonts.
+ */
+const sans = SansFont({
+ subsets: ['latin'],
+ variable: '--font-sans',
+ fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
+ preload: true,
+ weight: ['300', '400', '500', '600', '700'],
+});
+
+/**
+ * @heading
+ * @description Define here the heading font.
+ * By default, it uses the Urbanist font from Google Fonts.
+ */
+const heading = HeadingFont({
+ subsets: ['latin'],
+ variable: '--font-heading',
+ fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
+ preload: true,
+ weight: ['500', '700'],
+});
+
+// we export these fonts into the root layout
+export { sans, heading };
diff --git a/apps/web/lib/root-metdata.ts b/apps/web/lib/root-metdata.ts
new file mode 100644
index 000000000..d8aea6fcc
--- /dev/null
+++ b/apps/web/lib/root-metdata.ts
@@ -0,0 +1,32 @@
+import { Metadata } from 'next';
+
+import appConfig from '~/config/app.config';
+
+/**
+ * @name rootMetadata
+ * @description Define the root metadata for the application.
+ */
+export const rootMetadata: Metadata = {
+ title: appConfig.name,
+ description: appConfig.description,
+ metadataBase: new URL(appConfig.url),
+ openGraph: {
+ url: appConfig.url,
+ siteName: appConfig.name,
+ description: appConfig.description,
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: appConfig.title,
+ description: appConfig.description,
+ },
+ icons: {
+ icon: '/images/favicon/favicon.ico',
+ shortcut: '/shortcut-icon.png',
+ apple: '/images/favicon/apple-touch-icon.png',
+ other: {
+ rel: 'apple-touch-icon-precomposed',
+ url: '/apple-touch-icon-precomposed.png',
+ },
+ },
+};
diff --git a/packages/cms/core/src/cms-client.ts b/packages/cms/core/src/cms-client.ts
index 7d15732b2..aa8d886d8 100644
--- a/packages/cms/core/src/cms-client.ts
+++ b/packages/cms/core/src/cms-client.ts
@@ -6,7 +6,7 @@ export namespace Cms {
url: string;
description: string | undefined;
content: unknown;
- publishedAt: Date;
+ publishedAt: string;
image: string | undefined;
slug: string;
categories: Category[];
diff --git a/packages/cms/keystatic/src/client.ts b/packages/cms/keystatic/src/client.ts
index d5b816541..c8555cb6b 100644
--- a/packages/cms/keystatic/src/client.ts
+++ b/packages/cms/keystatic/src/client.ts
@@ -16,8 +16,6 @@ export class KeystaticClient implements CmsClient {
const docs = await reader.collections[collection].all();
- console.log(docs);
-
const startOffset = options?.offset ?? 0;
const endOffset = startOffset + (options?.limit ?? 10);
@@ -144,7 +142,7 @@ export class KeystaticClient implements CmsClient {
url: item.slug,
slug: item.slug,
description: item.entry.description,
- publishedAt,
+ publishedAt: publishedAt.toISOString(),
content,
image: item.entry.image ?? undefined,
categories: