diff --git a/src/app/(home)/activities/page.tsx b/src/app/(home)/activities/page.tsx index 2d0b6e1f..13d68c5c 100644 --- a/src/app/(home)/activities/page.tsx +++ b/src/app/(home)/activities/page.tsx @@ -1,10 +1,17 @@ +import { Metadata } from "next" + import { Hydrate, dehydrate } from "@tanstack/react-query" import { HomeFeed } from "~/components/home/HomeFeed" import { HomeSidebar } from "~/components/home/HomeSidebar" +import { APP_NAME } from "~/lib/env" import getQueryClient from "~/lib/query-client" import { prefetchGetFeed } from "~/queries/home.server" +export const metadata: Metadata = { + title: `Activities - ${APP_NAME}`, +} + async function Activities() { const queryClient = getQueryClient() await prefetchGetFeed( diff --git a/src/app/(home)/search/page.tsx b/src/app/(home)/search/page.tsx index 58147d1b..e49284a5 100644 --- a/src/app/(home)/search/page.tsx +++ b/src/app/(home)/search/page.tsx @@ -1,6 +1,21 @@ +import { Metadata } from "next" + import { SearchInput } from "~/components/common/SearchInput" import { HomeFeed } from "~/components/home/HomeFeed" import { HomeSidebar } from "~/components/home/HomeSidebar" +import { APP_NAME } from "~/lib/env" + +export function generateMetadata({ + searchParams, +}: { + searchParams: { + [key: string]: string | string[] | undefined + } +}): Metadata { + return { + title: `Search: ${searchParams.q} - ${APP_NAME}`, + } +} async function Search() { return ( diff --git a/src/app/(home)/topic/[topic]/page.tsx b/src/app/(home)/topic/[topic]/page.tsx index f2dcb6df..6870d8bb 100644 --- a/src/app/(home)/topic/[topic]/page.tsx +++ b/src/app/(home)/topic/[topic]/page.tsx @@ -1,8 +1,23 @@ +import { Metadata } from "next" + import { HomeFeed } from "~/components/home/HomeFeed" import { HomeSidebar } from "~/components/home/HomeSidebar" +import { APP_NAME } from "~/lib/env" import topics from "../../../../../data/topics.json" +export function generateMetadata({ + params, +}: { + params: { + topic: string + } +}): Metadata { + return { + title: `Topic: ${params.topic} - ${APP_NAME}`, + } +} + function Topic({ params, }: { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d2e70417..2562a84e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,22 +5,68 @@ import { Toaster } from "react-hot-toast" import "~/css/main.css" import { useAcceptLang } from "~/hooks/useAcceptLang" -import { APP_DESCRIPTION, APP_NAME, SITE_URL } from "~/lib/env" +import { APP_DESCRIPTION, APP_NAME, APP_SLOGAN, SITE_URL } from "~/lib/env" import Providers from "./providers" -// default site meta export const metadata: Metadata = { - title: APP_NAME, + title: `${APP_NAME} - ${APP_SLOGAN}`, description: APP_DESCRIPTION, - icons: `${SITE_URL}/assets/logo.svg`, // default site icon + applicationName: APP_NAME, + generator: APP_NAME, + keywords: [ + "blog", + "xlog", + "blockchain", + "ethereum", + "web3", + "dapp", + "crypto", + ], + themeColor: "#ffffff", + alternates: { + types: { + "application/rss+xml": [ + { url: "/feed/latest?format=xml", title: "xLog Latest" }, + { url: "/feed/hottest?interval=0&format=xml", title: "xLog Hottest" }, + { + url: "/feed/hottest?interval=1&format=xml", + title: "xLog Hottest of the Day", + }, + { + url: "/feed/hottest?interval=7&format=xml", + title: "xLog Hottest of the Week", + }, + { + url: "/feed/hottest?interval=30&format=xml", + title: "xLog Hottest of the Month", + }, + ], + "application/feed+json": [ + { url: "/feed/latest", title: "xLog Latest" }, + { url: "/feed/hottest?interval=0", title: "xLog Hottest" }, + { url: "/feed/hottest?interval=1", title: "xLog Hottest of the Day" }, + { url: "/feed/hottest?interval=7", title: "xLog Hottest of the Week" }, + { + url: "/feed/hottest?interval=30", + title: "xLog Hottest of the Month", + }, + ], + }, + }, + icons: `${SITE_URL}/assets/logo.svg`, openGraph: { - siteName: APP_NAME, - description: "", // modify by pages + siteName: `${APP_NAME} - ${APP_SLOGAN}`, + description: APP_DESCRIPTION, + images: [`${SITE_URL}/assets/logo.svg`], }, twitter: { - title: undefined, - description: undefined, + card: "summary_large_image", + title: `${APP_NAME} - ${APP_SLOGAN}`, + description: APP_DESCRIPTION, + images: [`${SITE_URL}/assets/logo.svg`], + site: "@_xLog", + creator: "@_xLog", }, } diff --git a/src/app/site/[site]/[slug]/page.tsx b/src/app/site/[site]/[slug]/page.tsx index 0d9bd173..06b94f37 100644 --- a/src/app/site/[site]/[slug]/page.tsx +++ b/src/app/site/[site]/[slug]/page.tsx @@ -1,13 +1,68 @@ +import { Metadata } from "next" import { notFound } from "next/navigation" import { Hydrate, dehydrate } from "@tanstack/react-query" import { SitePage } from "~/components/site/SitePage" +import { SITE_URL } from "~/lib/env" import { useTranslation } from "~/lib/i18n" import getQueryClient from "~/lib/query-client" import { fetchGetPage } from "~/queries/page.server" import { fetchGetSite } from "~/queries/site.server" +export async function generateMetadata({ + params, +}: { + params: { + site: string + slug: string + } +}): Promise { + const queryClient = getQueryClient() + + const site = await fetchGetSite(params.site, queryClient) + + const page = await fetchGetPage( + { + characterId: site?.characterId, + slug: params.slug, + useStat: true, + }, + queryClient, + ) + + const title = `${page?.metadata?.content?.title} - ${ + site?.metadata?.content?.name || site?.handle + }` + const description = page?.metadata?.content?.summary + const siteImages = + site?.metadata?.content?.avatars || `${SITE_URL}/assets/logo.svg` + const images = page?.metadata?.content?.cover || siteImages + const twitterCreator = + "@" + + site?.metadata?.content?.connected_accounts + ?.find((account) => account?.endsWith?.("@twitter")) + ?.match(/csb:\/\/account:([^@]+)@twitter/)?.[1] + + return { + title, + description, + openGraph: { + siteName: title, + description, + images, + }, + twitter: { + card: "summary_large_image", + title, + description, + images, + site: "@_xLog", + creator: twitterCreator, + }, + } +} + export default async function SitePagePage({ params, }: { diff --git a/src/app/site/[site]/archives/page.tsx b/src/app/site/[site]/archives/page.tsx index 93d02975..eb95025c 100644 --- a/src/app/site/[site]/archives/page.tsx +++ b/src/app/site/[site]/archives/page.tsx @@ -1,3 +1,5 @@ +import { Metadata } from "next" + import { Hydrate, dehydrate } from "@tanstack/react-query" import { SiteArchives } from "~/components/site/SiteArchives" @@ -6,6 +8,24 @@ import { PageVisibilityEnum } from "~/lib/types" import { prefetchGetPagesBySite } from "~/queries/page.server" import { fetchGetSite } from "~/queries/site.server" +export async function generateMetadata({ + params, +}: { + params: { + site: string + } +}): Promise { + const queryClient = getQueryClient() + + const site = await fetchGetSite(params.site, queryClient) + + const title = `Archives - ${site?.metadata?.content?.name || site?.handle}` + + return { + title, + } +} + export default async function SiteArchivesPage({ params, }: { diff --git a/src/app/site/[site]/layout.tsx b/src/app/site/[site]/layout.tsx index 8a211356..59e46ceb 100644 --- a/src/app/site/[site]/layout.tsx +++ b/src/app/site/[site]/layout.tsx @@ -1,3 +1,4 @@ +import { Metadata } from "next" import { notFound } from "next/navigation" import { Hydrate, dehydrate } from "@tanstack/react-query" @@ -8,6 +9,7 @@ import { BackToTopFAB } from "~/components/site/BackToTopFAB" import { SiteFooter } from "~/components/site/SiteFooter" import { SiteHeader } from "~/components/site/SiteHeader" import { FABContainer } from "~/components/ui/FAB" +import { SITE_URL } from "~/lib/env" import getQueryClient from "~/lib/query-client" import { cn } from "~/lib/utils" import { @@ -16,6 +18,60 @@ import { prefetchGetSiteToSubscriptions, } from "~/queries/site.server" +export async function generateMetadata({ + params, +}: { + params: { + site: string + } +}): Promise { + const queryClient = getQueryClient() + + const site = await fetchGetSite(params.site, queryClient) + + const title = site?.metadata?.content?.name || site?.handle + const description = site?.metadata?.content?.bio + const images = + site?.metadata?.content?.avatars || `${SITE_URL}/assets/logo.svg` + const twitterCreator = + "@" + + site?.metadata?.content?.connected_accounts + ?.find((account) => account?.endsWith?.("@twitter")) + ?.match(/csb:\/\/account:([^@]+)@twitter/)?.[1] + + return { + title, + description, + themeColor: "#ffffff", // TODO + alternates: { + types: { + "application/rss+xml": [ + { url: "/feed?format=xml", title }, + { url: "/feed/comments?format=xml", title: `Comments on ${title}` }, + ], + "application/feed+json": [ + { url: "/feed", title }, + { url: "/feed/comments", title: `Comments on ${title}` }, + ], + }, + }, + icons: images, + openGraph: { + siteName: title, + description, + images, + }, + twitter: { + card: "summary_large_image", + title, + description, + images, + site: "@_xLog", + creator: twitterCreator, + }, + } +} + export default async function SiteLayout({ children, params, @@ -71,22 +127,8 @@ export default async function SiteLayout({ // `xlog-page-${type}`, } > - {/* ]*>/g, "") - } - image={ - page.data?.metadata?.content?.cover || - getUserContentsUrl(site.data?.metadata?.content?.avatars?.[0]) - } - icon={getUserContentsUrl(site.data?.metadata?.content?.avatars?.[0])} - site={domainOrSubdomain} - /> */}