fix(seo): correct canonicals and navigation semantics
This commit is contained in:
@@ -10,6 +10,7 @@ import { GoogleAnalytics } from "@/components/google-analytics";
|
|||||||
import { PoyrazBottomRightFollower } from "@/components/poyraz-bottom-right-follower";
|
import { PoyrazBottomRightFollower } from "@/components/poyraz-bottom-right-follower";
|
||||||
import { listAnimationSources } from "@/data/animation-sources";
|
import { listAnimationSources } from "@/data/animation-sources";
|
||||||
import { getHomeBlogNews, getLatestAgendaArticle } from "@/data/blog";
|
import { getHomeBlogNews, getLatestAgendaArticle } from "@/data/blog";
|
||||||
|
import { SITE_URL } from "@/lib/seo";
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
@@ -24,7 +25,7 @@ export async function generateMetadata({
|
|||||||
const description = t("description");
|
const description = t("description");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metadataBase: new URL("https://poyrazavsever.com"),
|
metadataBase: new URL(SITE_URL),
|
||||||
title: {
|
title: {
|
||||||
default: title,
|
default: title,
|
||||||
template: titleTemplate,
|
template: titleTemplate,
|
||||||
@@ -34,7 +35,7 @@ export async function generateMetadata({
|
|||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
{ url: "/favicon.ico", sizes: "any" },
|
{ url: "/favicon.ico", sizes: "any" },
|
||||||
{ url: "/logo/logo.webp", type: "image/webp" },
|
{ url: "/logo/logo-96.webp", type: "image/webp", sizes: "96x96" },
|
||||||
],
|
],
|
||||||
shortcut: "/favicon.ico",
|
shortcut: "/favicon.ico",
|
||||||
apple: "/logo/apple-touch-icon.png",
|
apple: "/logo/apple-touch-icon.png",
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
import { SITE_URL } from "@/lib/seo";
|
||||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com";
|
|
||||||
|
|
||||||
export default function robots(): MetadataRoute.Robots {
|
export default function robots(): MetadataRoute.Robots {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getAllBlogArticles } from "@/data/blog";
|
import { getAllBlogArticles } from "@/data/blog";
|
||||||
|
|
||||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com";
|
import { SITE_URL } from "@/lib/seo";
|
||||||
const FEED_PATH = "/rss.xml";
|
const FEED_PATH = "/rss.xml";
|
||||||
const FEED_TITLE = "Poyraz Avsever Blog";
|
const FEED_TITLE = "Poyraz Avsever Blog";
|
||||||
const FEED_DESCRIPTION =
|
const FEED_DESCRIPTION =
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com";
|
import { SITE_URL } from "@/lib/seo";
|
||||||
|
|
||||||
function serializeJsonLd(data: object) {
|
function serializeJsonLd(data: object) {
|
||||||
return JSON.stringify(data).replace(/</g, "\\u003c");
|
return JSON.stringify(data).replace(/</g, "\\u003c");
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ function PromoRail({
|
|||||||
>
|
>
|
||||||
<div className="sticky top-4 py-4">
|
<div className="sticky top-4 py-4">
|
||||||
<div
|
<div
|
||||||
role="tablist"
|
role="group"
|
||||||
aria-label={t("slideNavigationLabel")}
|
aria-label={t("slideNavigationLabel")}
|
||||||
onKeyDown={handleNavigationKeyDown}
|
onKeyDown={handleNavigationKeyDown}
|
||||||
className="mb-3 flex h-5 items-center justify-center gap-2"
|
className="mb-3 flex h-5 items-center justify-center gap-2"
|
||||||
@@ -247,9 +247,7 @@ function PromoRail({
|
|||||||
<button
|
<button
|
||||||
key={`${railId}-dot-${index}`}
|
key={`${railId}-dot-${index}`}
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
aria-pressed={selected}
|
||||||
aria-selected={selected}
|
|
||||||
aria-controls={`${railId}-panel-${index}`}
|
|
||||||
aria-label={t("slideCta", { slide: index + 1 })}
|
aria-label={t("slideCta", { slide: index + 1 })}
|
||||||
onClick={() => setActiveSlide(index)}
|
onClick={() => setActiveSlide(index)}
|
||||||
className="group flex size-5 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
className="group flex size-5 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||||
@@ -266,12 +264,19 @@ function PromoRail({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span className="sr-only" aria-live="polite">
|
||||||
|
{t("slideStatus", {
|
||||||
|
current: activeSlide + 1,
|
||||||
|
total: slides.length,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
|
||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
<AnimatePresence mode="wait" initial={false}>
|
<AnimatePresence mode="wait" initial={false}>
|
||||||
<motion.div
|
<motion.div
|
||||||
key={`${railId}-slide-${activeSlide}`}
|
key={`${railId}-slide-${activeSlide}`}
|
||||||
id={`${railId}-panel-${activeSlide}`}
|
id={`${railId}-panel-${activeSlide}`}
|
||||||
role="tabpanel"
|
role="group"
|
||||||
aria-label={t("slideStatus", {
|
aria-label={t("slideStatus", {
|
||||||
current: activeSlide + 1,
|
current: activeSlide + 1,
|
||||||
total: slides.length,
|
total: slides.length,
|
||||||
|
|||||||
+25
-21
@@ -25,9 +25,6 @@ import {
|
|||||||
SheetContent,
|
SheetContent,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
Tabs,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
@@ -74,6 +71,18 @@ function getDesktopDropdownTriggerClass(isActive: boolean) {
|
|||||||
].join(" ");
|
].join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDesktopNavLinkClass(isActive: boolean) {
|
||||||
|
return [
|
||||||
|
"relative inline-flex h-9 shrink-0 items-center justify-center whitespace-nowrap rounded-sm px-2.5 text-xs font-medium outline-none",
|
||||||
|
"transition-[color,background-color] duration-[var(--poyraz-motion-duration-fast)] ease-[var(--poyraz-motion-ease-out)]",
|
||||||
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
"after:absolute after:inset-x-2.5 after:bottom-0 after:h-0.5 after:rounded-full after:transition-colors",
|
||||||
|
isActive
|
||||||
|
? "text-foreground after:bg-primary"
|
||||||
|
: "text-muted-foreground after:bg-transparent hover:text-foreground",
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
type ThemeToggleProps = {
|
type ThemeToggleProps = {
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
onThemeChange: (theme: ThemeMode) => void;
|
onThemeChange: (theme: ThemeMode) => void;
|
||||||
@@ -125,7 +134,6 @@ export function SiteNavbar({
|
|||||||
return pathname === href || pathname.startsWith(`${href}/`);
|
return pathname === href || pathname.startsWith(`${href}/`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeTab = NAV_LINKS.find((item) => isActiveLink(item.href))?.id;
|
|
||||||
const activeMobileMenuGroup = NAV_DROPDOWN_GROUPS.find(
|
const activeMobileMenuGroup = NAV_DROPDOWN_GROUPS.find(
|
||||||
(group) => group.id === mobileMenuGroupId,
|
(group) => group.id === mobileMenuGroupId,
|
||||||
);
|
);
|
||||||
@@ -204,7 +212,7 @@ export function SiteNavbar({
|
|||||||
className="inline-flex shrink-0 items-center"
|
className="inline-flex shrink-0 items-center"
|
||||||
>
|
>
|
||||||
<Logo
|
<Logo
|
||||||
src="/logo/logo.webp"
|
src="/logo/logo-96.webp"
|
||||||
alt="Poyraz Avsever"
|
alt="Poyraz Avsever"
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
@@ -216,12 +224,9 @@ export function SiteNavbar({
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="hidden min-w-0 flex-1 items-center justify-end gap-2 min-[840px]:flex">
|
<div className="hidden min-w-0 flex-1 items-center justify-end gap-2 min-[840px]:flex">
|
||||||
<Tabs value={activeTab ?? ""} className="w-auto shrink-0">
|
<nav
|
||||||
<TabsList
|
aria-label={locale === "tr" ? "Ana navigasyon" : "Main navigation"}
|
||||||
variant="line"
|
className="flex h-9 w-auto shrink-0 items-center gap-1"
|
||||||
radius="sm"
|
|
||||||
className="h-9 gap-1 bg-transparent p-0"
|
|
||||||
aria-label="Ana navigasyon"
|
|
||||||
>
|
>
|
||||||
{NAV_LINKS.map((item, index) => (
|
{NAV_LINKS.map((item, index) => (
|
||||||
<Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
@@ -233,15 +238,15 @@ export function SiteNavbar({
|
|||||||
decorative
|
decorative
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<TabsTrigger
|
<Link
|
||||||
value={item.id}
|
href={item.href}
|
||||||
asChild
|
aria-current={isActiveLink(item.href) ? "page" : undefined}
|
||||||
size="sm"
|
className={getDesktopNavLinkClass(
|
||||||
radius="sm"
|
isActiveLink(item.href),
|
||||||
className="cursor-pointer px-2.5"
|
)}
|
||||||
>
|
>
|
||||||
<Link href={item.href}>{t(item.id)}</Link>
|
{t(item.id)}
|
||||||
</TabsTrigger>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{NAV_DROPDOWN_GROUPS.filter(
|
{NAV_DROPDOWN_GROUPS.filter(
|
||||||
@@ -316,8 +321,7 @@ export function SiteNavbar({
|
|||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</nav>
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<Separator
|
<Separator
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
|||||||
+15
-3
@@ -1,9 +1,21 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
export const SITE_URL = (
|
function resolveSiteUrl() {
|
||||||
process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com"
|
const url = new URL(
|
||||||
).replace(/\/$/, "");
|
process.env.NEXT_PUBLIC_SITE_URL || "https://www.poyrazavsever.com",
|
||||||
|
);
|
||||||
|
|
||||||
|
// The public site redirects the apex domain to `www`. Canonicals must point
|
||||||
|
// directly to the final URL instead of a redirecting host.
|
||||||
|
if (url.hostname === "poyrazavsever.com") {
|
||||||
|
url.hostname = "www.poyrazavsever.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SITE_URL = resolveSiteUrl();
|
||||||
|
|
||||||
export type SiteLocale = "tr" | "en";
|
export type SiteLocale = "tr" | "en";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user