feat: enhance SEO with meta tags and sitemap, add DocumentMeta component

This commit is contained in:
TerryM
2026-05-28 22:28:23 +08:00
parent f1a0e9ab40
commit f183a401fc
6 changed files with 300 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ import { ChevronDown, Menu, Search as SearchIcon, X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
import { ArkLogoMark } from "../components/ArkLogoMark";
import { DocumentMeta } from "../components/DocumentMeta";
import { SearchPanel } from "../components/SearchPanel";
import { useI18n, type Lang } from "../i18n";
import { LANG_OPTIONS } from "../i18nLanguages";
@@ -274,6 +275,9 @@ export function PublicLayout() {
const [open, setOpen] = useState(false);
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [q, setQ] = useState("");
const menuRef = useRef<HTMLDivElement>(null);
const mobileMenuButtonRef = useRef<HTMLButtonElement>(null);
const desktopMenuButtonRef = useRef<HTMLButtonElement>(null);
const nav = useNavigate();
const na = (which: PublicNavWhich) =>
@@ -290,8 +294,35 @@ export function PublicLayout() {
setMobileSearchOpen(false);
};
useEffect(() => {
if (!open) return;
const closeOnOutside = (event: MouseEvent | TouchEvent) => {
const target = event.target as Node;
if (
menuRef.current?.contains(target) ||
mobileMenuButtonRef.current?.contains(target) ||
desktopMenuButtonRef.current?.contains(target)
) {
return;
}
setOpen(false);
};
const closeOnScroll = () => setOpen(false);
document.addEventListener("mousedown", closeOnOutside);
document.addEventListener("touchstart", closeOnOutside);
window.addEventListener("scroll", closeOnScroll, true);
return () => {
document.removeEventListener("mousedown", closeOnOutside);
document.removeEventListener("touchstart", closeOnOutside);
window.removeEventListener("scroll", closeOnScroll, true);
};
}, [open]);
return (
<div className="min-h-full flex flex-col">
<DocumentMeta />
<header className="sticky top-0 z-40 bg-[#08070c] backdrop-blur-md md:border-b md:border-ark-line md:bg-ark-nav/98">
<div className="flex h-[64px] items-center justify-between bg-[#08070c] px-4 py-3 md:hidden">
<Link
@@ -334,6 +365,7 @@ export function PublicLayout() {
}}
/>
<button
ref={mobileMenuButtonRef}
type="button"
className="inline-flex h-[40px] w-[40px] shrink-0 items-center justify-center rounded-full bg-[#191921] text-[#a8a9ae] outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070c]"
onClick={() => {
@@ -453,6 +485,7 @@ export function PublicLayout() {
className="hidden h-10 w-36 md:block lg:w-40"
/>
<button
ref={desktopMenuButtonRef}
type="button"
className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-ark-line bg-[#1a1b20] text-neutral-200 outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg min-[1200px]:hidden"
onClick={() => setOpen((v) => !v)}
@@ -466,7 +499,8 @@ export function PublicLayout() {
{open ? (
<div
className={`${dropdownAnimationClass} grid gap-2 border-t border-ark-line bg-ark-nav px-4 py-3 min-[440px]:px-5 sm:px-6 md:px-9 min-[1200px]:hidden`}
ref={menuRef}
className={`${dropdownAnimationClass} fixed inset-x-0 top-[64px] z-50 grid gap-2 border-y border-ark-line bg-ark-nav px-4 py-3 shadow-2xl shadow-black/50 min-[440px]:px-5 sm:px-6 md:top-[70px] md:px-9 min-[1200px]:hidden`}
>
<div className="mb-1 hidden items-center gap-2 rounded-full border border-ark-line bg-[#1a1b20] px-3 py-2 md:flex">
<button