From 30e0d480c285c51ee40521ef54a023226d66c4cb Mon Sep 17 00:00:00 2001 From: sofio Date: Tue, 3 Feb 2026 16:02:41 +0800 Subject: [PATCH] seo --- src/app/[locale]/about/page.tsx | 22 +++++++- src/app/[locale]/layout.tsx | 30 +++++++++- src/app/[locale]/page.tsx | 22 +++++++- src/app/[locale]/solutions/page.tsx | 20 +++++++ src/app/[locale]/tech/page.tsx | 22 +++++++- src/app/robots.ts | 15 +++++ src/app/sitemap.ts | 19 +++++++ src/lib/seo.ts | 86 +++++++++++++++++++++++++++++ src/messages/en.json | 11 +++- src/messages/zh-Hant.json | 11 +++- src/messages/zh.json | 11 +++- 11 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 src/app/robots.ts create mode 100644 src/app/sitemap.ts create mode 100644 src/lib/seo.ts diff --git a/src/app/[locale]/about/page.tsx b/src/app/[locale]/about/page.tsx index fc8767a..a4c0861 100644 --- a/src/app/[locale]/about/page.tsx +++ b/src/app/[locale]/about/page.tsx @@ -1,10 +1,30 @@ -import { setRequestLocale } from "next-intl/server"; +import type { Metadata } from "next"; +import { getTranslations, setRequestLocale } from "next-intl/server"; +import { Locale } from "@/i18n/config"; +import { createPageMetadata } from "@/lib/seo"; import AboutHeroSection from "@/components/AboutHeroSection"; import ParentCompanySection from "@/components/ParentCompanySection"; import TeamSection from "@/components/TeamSection"; import MilestonesSection from "@/components/MilestonesSection"; import RecruitSection from "@/components/RecruitSection"; +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "metadata" }); + + return createPageMetadata({ + locale: locale as Locale, + pathname: "/about", + title: t("aboutTitle"), + description: t("aboutDescription"), + siteName: t("title"), + }); +} + export default async function AboutPage({ params, }: { diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 01b5cd6..c11f3f3 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,9 +1,10 @@ import type { Metadata } from "next"; -import { NextIntlClientProvider, useMessages } from "next-intl"; +import { NextIntlClientProvider } from "next-intl"; import { getTranslations, setRequestLocale } from "next-intl/server"; import { notFound } from "next/navigation"; import { routing } from "@/i18n/routing"; import { Locale } from "@/i18n/config"; +import { getBaseUrl, ogImage } from "@/lib/seo"; import "../globals.css"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; @@ -25,10 +26,35 @@ export async function generateMetadata({ }): Promise { const { locale } = await params; const t = await getTranslations({ locale, namespace: "metadata" }); + const baseUrl = getBaseUrl(); + return { - title: t("title"), + metadataBase: baseUrl, + title: { + default: t("title"), + template: `%s | ${t("title")}`, + }, description: t("description"), + keywords: t("keywords"), + applicationName: t("title"), icons: { icon: "/LOGO_header.png" }, + openGraph: { + title: t("title"), + description: t("description"), + siteName: t("title"), + type: "website", + images: [{ url: ogImage, alt: t("title") }], + }, + twitter: { + card: "summary_large_image", + title: t("title"), + description: t("description"), + images: [ogImage], + }, + robots: { + index: true, + follow: true, + }, }; } diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index d40096a..2ca3b03 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -1,9 +1,29 @@ -import { setRequestLocale } from "next-intl/server"; +import type { Metadata } from "next"; +import { getTranslations, setRequestLocale } from "next-intl/server"; +import { Locale } from "@/i18n/config"; +import { createPageMetadata } from "@/lib/seo"; import HeroSection from "@/components/HeroSection"; import FlywheelSection from "@/components/FlywheelSection"; import MarketSection from "@/components/MarketSection"; import CTASection from "@/components/CTASection"; +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "metadata" }); + + return createPageMetadata({ + locale: locale as Locale, + pathname: "/", + title: t("homeTitle"), + description: t("homeDescription"), + siteName: t("title"), + }); +} + export default async function Home({ params, }: { diff --git a/src/app/[locale]/solutions/page.tsx b/src/app/[locale]/solutions/page.tsx index 348aa18..3c3fe6b 100644 --- a/src/app/[locale]/solutions/page.tsx +++ b/src/app/[locale]/solutions/page.tsx @@ -1,8 +1,28 @@ +import type { Metadata } from "next"; import { setRequestLocale, getTranslations } from "next-intl/server"; +import { Locale } from "@/i18n/config"; +import { createPageMetadata } from "@/lib/seo"; import SolutionsHeroSection from "@/components/SolutionsHeroSection"; import ScenarioSection from "@/components/ScenarioSection"; import SolutionsCTASection from "@/components/SolutionsCTASection"; +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "metadata" }); + + return createPageMetadata({ + locale: locale as Locale, + pathname: "/solutions", + title: t("solutionsTitle"), + description: t("solutionsDescription"), + siteName: t("title"), + }); +} + export default async function SolutionsPage({ params, }: { diff --git a/src/app/[locale]/tech/page.tsx b/src/app/[locale]/tech/page.tsx index 8fc1b4a..0055aba 100644 --- a/src/app/[locale]/tech/page.tsx +++ b/src/app/[locale]/tech/page.tsx @@ -1,10 +1,30 @@ -import { setRequestLocale } from "next-intl/server"; +import type { Metadata } from "next"; +import { getTranslations, setRequestLocale } from "next-intl/server"; +import { Locale } from "@/i18n/config"; +import { createPageMetadata } from "@/lib/seo"; import TechHeroSection from "@/components/TechHeroSection"; import ArchitectureSection from "@/components/ArchitectureSection"; import AICapabilitiesSection from "@/components/AICapabilitiesSection"; import Web3CapabilitiesSection from "@/components/Web3CapabilitiesSection"; import TechCTASection from "@/components/TechCTASection"; +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "metadata" }); + + return createPageMetadata({ + locale: locale as Locale, + pathname: "/tech", + title: t("techTitle"), + description: t("techDescription"), + siteName: t("title"), + }); +} + export default async function TechPage({ params, }: { diff --git a/src/app/robots.ts b/src/app/robots.ts new file mode 100644 index 0000000..d5f1c5e --- /dev/null +++ b/src/app/robots.ts @@ -0,0 +1,15 @@ +import type { MetadataRoute } from "next"; +import { siteUrl } from "@/lib/seo"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: "*", + allow: "/", + }, + ], + sitemap: `${siteUrl}/sitemap.xml`, + host: siteUrl, + }; +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts new file mode 100644 index 0000000..e0c8b71 --- /dev/null +++ b/src/app/sitemap.ts @@ -0,0 +1,19 @@ +import type { MetadataRoute } from "next"; +import { routing } from "@/i18n/routing"; +import { Locale } from "@/i18n/config"; +import { buildUrl, getLocalePath } from "@/lib/seo"; + +const staticRoutes = ["/", "/tech", "/solutions", "/about"] as const; + +export default function sitemap(): MetadataRoute.Sitemap { + const lastModified = new Date(); + + return staticRoutes.flatMap((pathname) => + routing.locales.map((locale) => ({ + url: buildUrl(getLocalePath(locale as Locale, pathname)), + lastModified, + changeFrequency: pathname === "/" ? "weekly" : "monthly", + priority: pathname === "/" ? 1 : 0.7, + })), + ); +} diff --git a/src/lib/seo.ts b/src/lib/seo.ts new file mode 100644 index 0000000..d2344be --- /dev/null +++ b/src/lib/seo.ts @@ -0,0 +1,86 @@ +import type { Metadata } from "next"; +import { routing } from "@/i18n/routing"; +import { defaultLocale, Locale } from "@/i18n/config"; + +const rawSiteUrl = + process.env.NEXT_PUBLIC_SITE_URL || + process.env.SITE_URL || + "http://localhost:3000"; + +const normalizedSiteUrl = rawSiteUrl.match(/^https?:\/\//) + ? rawSiteUrl + : `https://${rawSiteUrl}`; + +export const siteUrl = normalizedSiteUrl.replace(/\/+$/, ""); +export const ogImage = "/LOGO_header.png"; + +const trimSlashes = (value: string) => value.replace(/^\/+|\/+$/g, ""); + +export const getBaseUrl = () => { + try { + return new URL(siteUrl); + } catch { + return undefined; + } +}; + +export const getLocalePath = (locale: Locale, pathname: string) => { + const cleaned = trimSlashes(pathname); + const suffix = cleaned ? `/${cleaned}` : ""; + + if (locale === defaultLocale) { + return suffix || "/"; + } + + return `/${locale}${suffix}`; +}; + +export const buildUrl = (pathname: string) => `${siteUrl}${pathname}`; + +export const getAlternates = (pathname: string) => + Object.fromEntries( + routing.locales.map((locale) => [ + locale, + buildUrl(getLocalePath(locale as Locale, pathname)), + ]), + ) as Record; + +export const createPageMetadata = ({ + locale, + pathname, + title, + description, + siteName, +}: { + locale: Locale; + pathname: string; + title: string; + description: string; + siteName: string; +}): Metadata => { + const canonicalPath = getLocalePath(locale, pathname); + const canonicalUrl = buildUrl(canonicalPath); + + return { + title, + description, + alternates: { + canonical: canonicalUrl, + languages: getAlternates(pathname), + }, + openGraph: { + title, + description, + url: canonicalUrl, + siteName, + type: "website", + images: [{ url: ogImage, alt: siteName }], + }, + twitter: { + card: "summary_large_image", + title, + description, + images: [ogImage], + }, + }; +}; diff --git a/src/messages/en.json b/src/messages/en.json index 40b9490..fcbc31c 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -1,7 +1,16 @@ { "metadata": { "title": "DESUN SINGULARITY", - "description": "AI + Web3 Powered Full-Chain Asset Technology Platform" + "description": "AI + Web3 Powered Full-Chain Asset Technology Platform", + "homeTitle": "Reshaping Global Asset Value Networks", + "homeDescription": "AI + Web3 Powered Full-Chain Asset Technology Platform", + "techTitle": "Technology Stack", + "techDescription": "Full-stack architecture combining AI, Web3, and asset operations for scalable real-world asset digitization.", + "solutionsTitle": "Solutions", + "solutionsDescription": "DeSpace smart space management, RWA services, and AI-driven quantitative management for asset value creation.", + "aboutTitle": "About DESUN Singularity", + "aboutDescription": "Technology-driven alternative asset management platform integrating AI, Web3, finance, and real estate operations.", + "keywords": "AI, Web3, RWA, asset digitization, tokenization, quantitative management, smart space" }, "header": { "home": "Home", diff --git a/src/messages/zh-Hant.json b/src/messages/zh-Hant.json index 2e30a22..13a6f53 100644 --- a/src/messages/zh-Hant.json +++ b/src/messages/zh-Hant.json @@ -1,7 +1,16 @@ { "metadata": { "title": "DESUN SINGULARITY", - "description": "AI + Web3 驅動的全鏈路資產科技平台" + "description": "AI + Web3 驅動的全鏈路資產科技平台", + "homeTitle": "重構全球資產價值網絡", + "homeDescription": "AI + Web3 驅動的全鏈路資產科技平台", + "techTitle": "技術體系", + "techDescription": "AI 與 Web3 貫通的全棧技術架構,支撐實體資產數字化與規模化營運。", + "solutionsTitle": "場景解決方案", + "solutionsDescription": "DeSpace 空間管理、RWA 資產代幣化、量化資管等核心場景解決方案。", + "aboutTitle": "關於德商奇點", + "aboutDescription": "科技驅動的另類資產管理平台,融合 AI、Web3、金融與實體營運能力。", + "keywords": "AI, Web3, RWA, 資產數字化, 資產代幣化, 量化資管, 空間管理" }, "header": { "home": "首頁", diff --git a/src/messages/zh.json b/src/messages/zh.json index 9acf649..0fcdaef 100644 --- a/src/messages/zh.json +++ b/src/messages/zh.json @@ -1,7 +1,16 @@ { "metadata": { "title": "DESUN SINGULARITY", - "description": "AI + Web3 驱动的全链路资产科技平台" + "description": "AI + Web3 驱动的全链路资产科技平台", + "homeTitle": "重构全球资产价值网络", + "homeDescription": "AI + Web3 驱动的全链路资产科技平台", + "techTitle": "技术体系", + "techDescription": "AI 与 Web3 贯通的全栈技术架构,支撑实体资产数字化与规模化运营。", + "solutionsTitle": "场景解决方案", + "solutionsDescription": "DeSpace 空间管理、RWA 资产代币化、量化资管等核心场景解决方案。", + "aboutTitle": "关于德商奇点", + "aboutDescription": "科技驱动的另类资产管理平台,融合 AI、Web3、金融与实体运营能力。", + "keywords": "AI, Web3, RWA, 资产数字化, 资产代币化, 量化资管, 空间管理" }, "header": { "home": "首页",