diff --git a/README.md b/README.md index 839f8b3..a0279c0 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,124 @@ -# Onlook Starter Template - -

- -

- -This is an [Onlook](https://onlook.com/) project set up with -[Next.js](https://nextjs.org/), [TailwindCSS](https://tailwindcss.com/) and -[ShadCN](https://ui.shadcn.com). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) in Onlook to see the result. +# Seven - 多语言官网(Next.js 15) + +

+ +

+ +基于 Next.js 15、Tailwind CSS 和 shadcn/ui 搭建的多语言企业官网模板,内置中/英/日/韩/繁体语言路由,统一页头/页尾布局与 SEO 元数据管理。 + +## 技术栈 + +- Next.js 15(App Router) +- React 18 +- Tailwind CSS +- shadcn/ui + +## 目录结构(关键项) + +``` +app/ + [lang]/ + layout.tsx # 按语言生成 Metadata(需 await params) + page.tsx # 首页入口(需 await params) + products/page.tsx # 产品页(Client 组件) + solutions/page.tsx # 解决方案页(Client 组件) + contact/page.tsx # 联系我们(使用 PageLayout,表单仅在客户端渲染) +components/ + PageLayout.tsx # 统一 TDK、Navbar、Footer 与内容容器 + Navbar.tsx + Footer.tsx + TDK.tsx # 页面标题/描述/关键词 +data/ + content.ts # 站点通用文案(多语言) + pageContent.ts # 各页面文案(多语言) +``` + +## 快速开始(Windows PowerShell) + +```powershell +# 安装依赖 +npm install + +# 本地开发(默认 http://localhost:3000) +npm run dev + +# 生产构建 +npm run build + +# 本地预览生产构建 +npm run start +``` + +## 环境要求 + +- Node.js ≥ 18.17(推荐 LTS) +- npm ≥ 9(或使用 pnpm/bun 亦可) + +## 多语言与路由 + +- 路由格式:`/[lang]/...`,其中 `lang ∈ { en, zh-CN, zh-TW, ko, ja }` +- 入口页静态参数:`app/[lang]/page.tsx` 中的 `generateStaticParams()` +- 元数据生成:`app/[lang]/layout.tsx` 中的 `generateMetadata()` 会根据语言返回对应 TDK + +注意:Next.js 15 起,动态段的 `params` 在服务端为 Promise,必须 `await` 后再使用,例如: + +```ts +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params; + // ... +} +``` + +## SEO 与 TDK + +- 统一在 `PageLayout` 内通过 `TDK` 组件注入 `title/description/keywords` +- `app/[lang]/layout.tsx` 中维护各语言默认元数据与 `alternates.languages` + +## SSR/CSR 与 Hydration 注意事项 + +- 避免在 SSR 阶段使用不稳定输出(如 `Date.now()`、`Math.random()`) +- 对于仅在客户端可用的交互(如带自动填充的表单),采用“客户端渲染占位”策略: + - `contact/page.tsx` 中使用 `isClient` 状态在挂载后再渲染表单,避免浏览器扩展(如 LastPass)注入导致水合不一致 +- 若你引入新的服务端函数,确保在使用 `params` 前执行 `await` + +## 常见问题(FAQ) + +- Hydration failed: + - 检查是否使用了未固定输出的变量; + - 检查是否有浏览器扩展注入 DOM(如密码管理器); + - 对仅客户端渲染的复杂表单使用 `isClient` 保护渲染; + - 移除不必要的 `suppressHydrationWarning`,定位并修复根因。 + +- Route used `params.lang`. `params` should be awaited: + - Next.js 15 的动态 API 是异步的,请调整类型为 `Promise<...>` 并 `await`。 + +## 开发脚本 + +```powershell +# 代码检查(如已配置) +npm run lint + +# Tailwind 即时开发(随 dev 一起运行) +npm run dev +``` + +## 部署 + +- 推荐部署到 Vercel: + - Framework: Next.js + - Build Command: `npm run build` + - Output: `.next` + - Node Version: 使用项目/平台默认 LTS +- 也可自托管: + - 运行 `npm run build && npm run start` + +## 贡献与代码风格 + +- 变量/函数命名清晰完整,避免缩写 +- 避免深层嵌套,使用早返回 +- TypeScript 明确标注导出 API 的类型 +- 不捕获且吞掉错误;错误需被有意义处理 + +## 许可证 + +本仓库以 MIT 协议开源。根据业务需要可自由修改与商用。 diff --git a/app/[lang]/contact/page.tsx b/app/[lang]/contact/page.tsx new file mode 100644 index 0000000..a09c2e6 --- /dev/null +++ b/app/[lang]/contact/page.tsx @@ -0,0 +1,409 @@ +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { useState, useEffect } from 'react'; +import PageLayout from '../../../components/PageLayout'; +import Icon from '../../../components/Icon'; +import { content } from '../../../data/content'; +import { contactContent } from '../../../data/pageContent'; + +export default function ContactPage() { + const router = useRouter(); + const params = useParams(); + const currentLang = typeof params.lang === 'string' ? params.lang : 'en'; + + // Form state + const [formData, setFormData] = useState({ + name: '', + email: '', + phone: '', + company: '', + subject: '', + message: '', + }); + const [formSubmitted, setFormSubmitted] = useState(false); + const [isClient, setIsClient] = useState(false); + + // Set client-side flag + useEffect(() => { + setIsClient(true); + }, []); + + // Validate language and redirect if invalid + useEffect(() => { + const supportedLangs = ['zh-CN', 'zh-TW', 'en', 'ko', 'ja']; + if (!supportedLangs.includes(currentLang)) { + router.push('/en/contact'); + } + }, [currentLang, router]); + + const currentContent = content[currentLang as keyof typeof content] || content.en; + const pageContent = + contactContent[currentLang as keyof typeof contactContent] || contactContent.en; + + const handleLanguageChange = (lang: string) => { + router.push(`/${lang}/contact`); + }; + + const handleInputChange = ( + e: React.ChangeEvent, + ) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // Here you would typically send the form data to your backend + console.log('Form submitted:', formData); + setFormSubmitted(true); + + // Reset form after submission + setTimeout(() => { + setFormData({ + name: '', + email: '', + phone: '', + company: '', + subject: '', + message: '', + }); + setFormSubmitted(false); + }, 5000); + }; + + return ( + +
+ {/* Page Header */} +
+

{pageContent.heading}

+

+ {pageContent.subheading} +

+
+ +
+ {/* Contact Information */} +
+

+ {currentLang === 'en' + ? 'Contact Information' + : currentLang === 'zh-CN' + ? '联系信息' + : currentLang === 'zh-TW' + ? '聯繫信息' + : currentLang === 'ko' + ? '연락처 정보' + : '連絡先情報'} +

+ +
+
+
+ +
+
+

+ {currentLang === 'en' + ? 'Address' + : currentLang === 'zh-CN' + ? '地址' + : currentLang === 'zh-TW' + ? '地址' + : currentLang === 'ko' + ? '주소' + : '住所'} +

+

+ {pageContent.contactInfo.address} +

+
+
+ +
+
+ +
+
+

+ {currentLang === 'en' + ? 'Phone' + : currentLang === 'zh-CN' + ? '电话' + : currentLang === 'zh-TW' + ? '電話' + : currentLang === 'ko' + ? '전화' + : '電話'} +

+

{pageContent.contactInfo.phone}

+
+
+ +
+
+ +
+
+

+ {currentLang === 'en' + ? 'Email' + : currentLang === 'zh-CN' + ? '电子邮箱' + : currentLang === 'zh-TW' + ? '電子郵箱' + : currentLang === 'ko' + ? '이메일' + : 'メール'} +

+

{pageContent.contactInfo.email}

+
+
+ +
+
+ +
+
+

+ {currentLang === 'en' + ? 'Business Hours' + : currentLang === 'zh-CN' + ? '营业时间' + : currentLang === 'zh-TW' + ? '營業時間' + : currentLang === 'ko' + ? '영업 시간' + : '営業時間'} +

+

{pageContent.contactInfo.hours}

+
+
+
+ + {/* Map Placeholder */} +
+

+ {currentLang === 'en' + ? 'Map will be displayed here' + : currentLang === 'zh-CN' + ? '地图将显示在这里' + : currentLang === 'zh-TW' + ? '地圖將顯示在這裡' + : currentLang === 'ko' + ? '지도가 여기에 표시됩니다' + : '地図がここに表示されます'} +

+
+
+ + {/* Contact Form */} +
+

+ {pageContent.formTitle} +

+ + {!isClient ? ( +
+
+
+
+ {pageContent.formFields.name.label} +
+
+ {pageContent.formFields.name.placeholder} +
+
+
+
+ {pageContent.formFields.email.label} +
+
+ {pageContent.formFields.email.placeholder} +
+
+
+
+
Loading form...
+
+
+ ) : formSubmitted ? ( +
+ + {currentLang === 'en' + ? 'Thank you!' + : currentLang === 'zh-CN' + ? '谢谢!' + : currentLang === 'zh-TW' + ? '謝謝!' + : currentLang === 'ko' + ? '감사합니다!' + : 'ありがとうございます!'} + + + {' '} + {currentLang === 'en' + ? 'Your message has been sent. We will contact you soon.' + : currentLang === 'zh-CN' + ? '您的消息已发送。我们将尽快与您联系。' + : currentLang === 'zh-TW' + ? '您的訊息已發送。我們將盡快與您聯繫。' + : currentLang === 'ko' + ? '메시지가 전송되었습니다. 곧 연락 드리겠습니다.' + : 'メッセージが送信されました。すぐにご連絡いたします。'} + +
+ ) : ( +
+
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ )} +
+
+
+
+ ); +} diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx new file mode 100644 index 0000000..cb95df8 --- /dev/null +++ b/app/[lang]/layout.tsx @@ -0,0 +1,66 @@ +import type { Metadata } from 'next'; + +interface LanguageLayoutProps { + children: React.ReactNode; + params: { lang: string }; +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string }>; +}): Promise { + // Map of language-specific metadata + const metadataByLang: Record = { + en: { + title: 'AWS Cloud Services Partner - Professional Cloud Computing Solutions', + description: + 'Providing comprehensive AWS cloud services including EC2, RDS, S3, AI/ML and more', + keywords: 'AWS, cloud computing, EC2, RDS, S3, cloud services, AI, machine learning', + }, + 'zh-CN': { + title: 'AWS云服务合作伙伴 - 专业云计算解决方案', + description: '提供AWS云服务器、数据库、存储、AI/ML等全方位云计算服务', + keywords: 'AWS,云服务器,云计算,数据库,存储,AI,机器学习', + }, + 'zh-TW': { + title: 'AWS雲端服務合作夥伴 - 專業雲端運算解決方案', + description: '提供AWS雲端伺服器、資料庫、儲存、AI/ML等全方位雲端運算服務', + keywords: 'AWS,雲端伺服器,雲端運算,資料庫,儲存,AI,機器學習', + }, + ko: { + title: 'AWS 클라우드 서비스 파트너 - 전문 클라우드 컴퓨팅 솔루션', + description: 'EC2, RDS, S3, AI/ML 등 포괄적인 AWS 클라우드 서비스 제공', + keywords: 'AWS,클라우드 컴퓨팅,EC2,RDS,S3,클라우드 서비스,AI,머신러닝', + }, + ja: { + title: 'AWSクラウドサービスパートナー - プロフェッショナルクラウドコンピューティングソリューション', + description: 'EC2、RDS、S3、AI/MLなど包括的なAWSクラウドサービスを提供', + keywords: 'AWS,クラウドコンピューティング,EC2,RDS,S3,クラウドサービス,AI,機械学習', + }, + }; + + // Await params before using its properties + const { lang } = await params; + + // Get metadata for the current language or fall back to English + const metadata = metadataByLang[lang] || metadataByLang.en; + + return { + ...metadata, + alternates: { + languages: { + en: '/en', + 'zh-CN': '/zh-CN', + 'zh-TW': '/zh-TW', + ko: '/ko', + ja: '/ja', + 'x-default': '/en', + }, + }, + }; +} + +export default function LanguageLayout({ children, params }: LanguageLayoutProps) { + return <>{children}; +} diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx new file mode 100644 index 0000000..18be5e0 --- /dev/null +++ b/app/[lang]/page.tsx @@ -0,0 +1,13 @@ +import HomeClient from '../../components/HomeClient'; + +export async function generateStaticParams() { + const supportedLangs = ['en', 'zh-CN', 'zh-TW', 'ko', 'ja']; + return supportedLangs.map((lang) => ({ + lang: lang, + })); +} + +export default async function Page({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params; + return ; +} diff --git a/app/[lang]/products/page.tsx b/app/[lang]/products/page.tsx new file mode 100644 index 0000000..2e32c77 --- /dev/null +++ b/app/[lang]/products/page.tsx @@ -0,0 +1,109 @@ +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { useState, useEffect } from 'react'; +import PageLayout from '../../../components/PageLayout'; +import Icon from '../../../components/Icon'; +import { content } from '../../../data/content'; +import { productsContent } from '../../../data/pageContent'; + +export default function ProductsPage() { + const router = useRouter(); + const params = useParams(); + const currentLang = typeof params.lang === 'string' ? params.lang : 'en'; + + // Validate language and redirect if invalid + useEffect(() => { + const supportedLangs = ['zh-CN', 'zh-TW', 'en', 'ko', 'ja']; + if (!supportedLangs.includes(currentLang)) { + router.push('/en/products'); + } + }, [currentLang, router]); + + const currentContent = content[currentLang as keyof typeof content] || content.en; + const pageContent = + productsContent[currentLang as keyof typeof productsContent] || productsContent.en; + + const handleLanguageChange = (lang: string) => { + router.push(`/${lang}/products`); + }; + + return ( + +
+ {/* Page Header */} +
+

{pageContent.heading}

+

+ {pageContent.subheading} +

+
+ + {/* Product Categories */} +
+ {pageContent.categories.map((category, index) => ( +
+
+

{category.title}

+

{category.description}

+
+
+ {category.products.map((product, productIndex) => ( +
+
+ +
+

+ {product.name} +

+

{product.description}

+
+ ))} +
+
+ ))} +
+ + {/* CTA Section */} +
+

{currentContent.hero.cta}

+

+ {currentLang === 'en' + ? 'Contact our AWS experts to discuss your specific cloud computing needs and requirements.' + : currentLang === 'zh-CN' + ? '联系我们的AWS专家,讨论您的特定云计算需求和要求。' + : currentLang === 'zh-TW' + ? '聯繫我們的AWS專家,討論您的特定雲計算需求和要求。' + : currentLang === 'ko' + ? 'AWS 전문가에게 문의하여 특정 클라우드 컴퓨팅 요구 사항에 대해 논의하세요.' + : '特定のクラウドコンピューティングのニーズと要件について、AWS専門家にお問い合わせください。'} +

+ +
+
+
+ ); +} diff --git a/app/[lang]/solutions/page.tsx b/app/[lang]/solutions/page.tsx new file mode 100644 index 0000000..730ca1f --- /dev/null +++ b/app/[lang]/solutions/page.tsx @@ -0,0 +1,135 @@ +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { useState, useEffect } from 'react'; +import PageLayout from '../../../components/PageLayout'; +import Icon from '../../../components/Icon'; +import { content } from '../../../data/content'; +import { solutionsContent } from '../../../data/pageContent'; + +export default function SolutionsPage() { + const router = useRouter(); + const params = useParams(); + const currentLang = typeof params.lang === 'string' ? params.lang : 'en'; + + // Validate language and redirect if invalid + useEffect(() => { + const supportedLangs = ['zh-CN', 'zh-TW', 'en', 'ko', 'ja']; + if (!supportedLangs.includes(currentLang)) { + router.push('/en/solutions'); + } + }, [currentLang, router]); + + const currentContent = content[currentLang as keyof typeof content] || content.en; + const pageContent = + solutionsContent[currentLang as keyof typeof solutionsContent] || solutionsContent.en; + + const handleLanguageChange = (lang: string) => { + router.push(`/${lang}/solutions`); + }; + + return ( + +
+ {/* Page Header */} +
+

{pageContent.heading}

+

+ {pageContent.subheading} +

+
+ + {/* Industries Grid */} +
+ {pageContent.industries.map((industry, index) => ( +
+
+
+ +
+

{industry.name}

+
+
+

{industry.description}

+
+

+ {currentLang === 'en' + ? 'Key Benefits:' + : currentLang === 'zh-CN' + ? '主要优势:' + : currentLang === 'zh-TW' + ? '主要優勢:' + : currentLang === 'ko' + ? '주요 이점:' + : '主な利点:'} +

+ {industry.benefits.map((benefit, benefitIndex) => ( +
+
+ +
+

{benefit}

+
+ ))} +
+
+
+ ))} +
+ + {/* CTA Section */} +
+

+ {currentLang === 'en' + ? 'Need a customized solution for your industry?' + : currentLang === 'zh-CN' + ? '需要为您的行业定制解决方案?' + : currentLang === 'zh-TW' + ? '需要為您的行業定制解決方案?' + : currentLang === 'ko' + ? '귀하의 산업에 맞는 맞춤형 솔루션이 필요하신가요?' + : 'あなたの業界向けのカスタマイズされたソリューションが必要ですか?'} +

+

+ {currentLang === 'en' + ? 'Our AWS experts can help you design and implement a cloud solution tailored to your specific industry requirements.' + : currentLang === 'zh-CN' + ? '我们的AWS专家可以帮助您设计和实施针对特定行业需求的云解决方案。' + : currentLang === 'zh-TW' + ? '我們的AWS專家可以幫助您設計和實施針對特定行業需求的雲解決方案。' + : currentLang === 'ko' + ? '당사의 AWS 전문가가 귀하의 특정 산업 요구 사항에 맞게 설계된 클라우드 솔루션을 설계하고 구현하는 데 도움을 드릴 수 있습니다.' + : '当社のAWS専門家は、特定の業界要件に合わせたクラウドソリューションの設計と実装をお手伝いします。'} +

+ +
+
+
+ ); +} diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000..1b3613c Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..b37ec16 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,88 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..6aa657b --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,91 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export async function generateMetadata({ + params, +}: { + params: { lang: string }; +}): Promise { + const domain = 'https://your-domain.com'; + // Map of language-specific metadata + const metadataByLang: Record = { + en: { + title: 'AWS Cloud Services Partner - Professional Cloud Computing Solutions', + description: + 'Providing comprehensive AWS cloud services including EC2, RDS, S3, AI/ML and more', + keywords: 'AWS, cloud computing, EC2, RDS, S3, cloud services, AI, machine learning', + }, + 'zh-CN': { + title: 'AWS云服务合作伙伴 - 专业云计算解决方案', + description: '提供AWS云服务器、数据库、存储、AI/ML等全方位云计算服务', + keywords: 'AWS,云服务器,云计算,数据库,存储,AI,机器学习', + }, + 'zh-TW': { + title: 'AWS雲端服務合作夥伴 - 專業雲端運算解決方案', + description: '提供AWS雲端伺服器、資料庫、儲存、AI/ML等全方位雲端運算服務', + keywords: 'AWS,雲端伺服器,雲端運算,資料庫,儲存,AI,機器學習', + }, + ko: { + title: 'AWS 클라우드 서비스 파트너 - 전문 클라우드 컴퓨팅 솔루션', + description: 'EC2, RDS, S3, AI/ML 등 포괄적인 AWS 클라우드 서비스 제공', + keywords: 'AWS,클라우드 컴퓨팅,EC2,RDS,S3,클라우드 서비스,AI,머신러닝', + }, + ja: { + title: 'AWSクラウドサービスパートナー - プロフェッショナルクラウドコンピューティングソリューション', + description: 'EC2、RDS、S3、AI/MLなど包括的なAWSクラウドサービスを提供', + keywords: 'AWS,クラウドコンピューティング,EC2,RDS,S3,クラウドサービス,AI,機械学習', + }, + }; + + // Get metadata for the current language or fall back to English + const metadata = metadataByLang[params.lang] || metadataByLang.en; + + return { + metadataBase: new URL(domain), + ...metadata, + alternates: { + languages: { + en: '/en', + 'zh-CN': '/zh-CN', + 'zh-TW': '/zh-TW', + ko: '/ko', + ja: '/ja', + 'x-default': '/en', + }, + }, + openGraph: { + title: metadata.title || undefined, + description: metadata.description || undefined, + url: `${domain}/${params.lang}`, + siteName: 'AWS Cloud Services Partner', + images: [ + { + url: '/og-image.png', // Must be an absolute URL + width: 1200, + height: 630, + }, + ], + locale: params.lang, + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: metadata.title || undefined, + description: metadata.description || undefined, + images: ['/og-image.png'], // Must be an absolute URL + }, + }; +} + +export default function RootLayout({ + children, + params, +}: Readonly<{ children: React.ReactNode; params: { lang: string } }>) { + return ( + + + {children} + + + ); +} diff --git a/app/robots.ts b/app/robots.ts new file mode 100644 index 0000000..d5fe348 --- /dev/null +++ b/app/robots.ts @@ -0,0 +1,14 @@ +import { MetadataRoute } from 'next'; + +const domain = 'https://your-domain.com'; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: '*', + allow: '/', + disallow: '/api/', + }, + sitemap: `${domain}/sitemap.xml`, + }; +} \ No newline at end of file diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 0000000..607e2a0 --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,18 @@ +import { MetadataRoute } from 'next'; + +const defaultLang = 'en'; +const domain = 'https://your-domain.com'; +const supportedLangs = ['en', 'zh-CN', 'zh-TW', 'ko', 'ja']; + +export default function sitemap(): MetadataRoute.Sitemap { + const routes = ['', '/products', '/solutions', '/contact']; + + const sitemapEntries = supportedLangs.flatMap((lang) => { + return routes.map((route) => ({ + url: `${domain}/${lang}${route}`, + lastModified: new Date(), + })); + }); + + return sitemapEntries; +} \ No newline at end of file