Skip to content

Commit

Permalink
fix(head): use self-referncing canonical urls (#2654)
Browse files Browse the repository at this point in the history
  • Loading branch information
raisedadead authored Jan 9, 2025
1 parent cdbea02 commit 9925429
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 7 deletions.
42 changes: 42 additions & 0 deletions src/__tests__/components/head.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ vi.mock('next/head', () => ({
}
}));

const mockRouter = {
asPath: '/'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('MetaHead', () => {
const defaultProps = {
pageTitle: undefined,
Expand All @@ -25,6 +33,8 @@ describe('MetaHead', () => {
beforeEach(() => {
// Clear any previous head elements
document.head.innerHTML = '';
// Reset router mock to default state
mockRouter.asPath = '/';
});

it('renders default meta tags', () => {
Expand Down Expand Up @@ -110,4 +120,36 @@ describe('MetaHead', () => {
?.getAttribute('content')
).toBe('#32ded4');
});

it('renders blog canonical URLs correctly', () => {
const props = {
setCanonicalBlogBaseURL: true,
blogSlug: 'test-post'
};
renderMetaHead(props);

expect(
document.querySelector('link[rel="canonical"]')?.getAttribute('href')
).toBe('https://hn.mrugesh.dev/test-post');
});

it('renders blog base URL when no slug is provided', () => {
const props = {
setCanonicalBlogBaseURL: true
};
renderMetaHead(props);

expect(
document.querySelector('link[rel="canonical"]')?.getAttribute('href')
).toBe('https://hn.mrugesh.dev');
});

it('renders clean URLs without query strings', () => {
mockRouter.asPath = '/about?ref=twitter';
renderMetaHead();

expect(
document.querySelector('link[rel="canonical"]')?.getAttribute('href')
).toBe('https://mrugesh.dev/about');
});
});
9 changes: 9 additions & 0 deletions src/__tests__/pages/about.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router
const mockRouter = {
asPath: '/about'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('About', () => {
it('renders the About page', () => {
render(<About />);
Expand Down
16 changes: 16 additions & 0 deletions src/__tests__/pages/blog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router with blog-specific properties
const mockRouter = {
asPath: '/blog',
query: {},
push: vi.fn(),
replace: vi.fn(),
prefetch: vi.fn()
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('Blog', () => {
const mockPost = {
title: 'Test Post',
Expand All @@ -49,6 +62,9 @@ describe('Blog', () => {
beforeEach(() => {
// Reset all mocks before each test
vi.resetAllMocks();
mockRouter.push.mockReset();
mockRouter.replace.mockReset();
mockRouter.prefetch.mockReset();

// Mock the fetchPostsList function to return the mockFallback data
(fetchPostsList as MockedFunction<typeof fetchPostsList>).mockResolvedValue(
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/pages/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router
const mockRouter = {
asPath: '/'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('Home', () => {
it('renders the name on the homepage', () => {
render(<Home />);
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/pages/privacy.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router
const mockRouter = {
asPath: '/privacy'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('Privacy', () => {
it('renders a heading', () => {
render(<Privacy />);
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/pages/refunds.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router
const mockRouter = {
asPath: '/refunds'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('Refunds', () => {
it('renders a heading', () => {
render(<Refunds />);
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/pages/terms.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router
const mockRouter = {
asPath: '/terms'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('Terms', () => {
it('renders a heading', () => {
render(<Terms />);
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/pages/uses.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ vi.mock('@/hooks/useDarkMode', () => ({
default: () => ({ isDarkMode: false, toggle: vi.fn() })
}));

// Mock next/router
const mockRouter = {
asPath: '/uses'
};

vi.mock('next/router', () => ({
useRouter: () => mockRouter
}));

describe('Uses', () => {
it('renders the Uses page', () => {
render(<Uses />);
Expand Down
50 changes: 45 additions & 5 deletions src/components/head.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,68 @@
import Head from 'next/head';
import { useRouter } from 'next/router';

interface MetaHeadProps {
pageTitle?: string;
pageDescription?: string;
pageUrl?: string;
pageImage?: string;
setCanonicalBlogBaseURL?: boolean;
blogSlug?: string;
}

export const MetaHead: React.FC<MetaHeadProps> = ({
pageTitle,
pageDescription,
pageUrl,
pageImage
pageImage,
setCanonicalBlogBaseURL,
blogSlug
}) => {
const router = useRouter();
const defaultTitle =
'Mrugesh Mohapatra — Portfolio of a nocturnal developer.';
const defaultDescription =
'Namaste! I am a technologist based out of Bengaluru, India. I am passionate about Aviation, Open Source, Education for All, and Site Reliability Engineering.';
const defaultUrl = 'https://mrugesh.dev';
const defaultImage = 'http://mrugesh.dev/images/og-image.webp';
const blogBaseUrl = 'https://hn.mrugesh.dev';

const getCanonicalUrl = (): string => {
// For blog pages, return the blog URLs
if (setCanonicalBlogBaseURL) {
if (blogSlug) {
return `${blogBaseUrl}/${blogSlug}`;
}
return blogBaseUrl;
}

// For explicitly provided URLs, use them directly
if (pageUrl) {
return pageUrl;
}

// For regular pages, construct the URL
const path = router.asPath || '/';

// If it's the home page, return just the domain
if (path === '/') {
return defaultUrl;
}

// Clean the path: remove query string and trailing slash
const pathWithoutQuery = path.includes('?')
? path.substring(0, path.indexOf('?'))
: path;
const cleanPath = pathWithoutQuery.endsWith('/')
? pathWithoutQuery.slice(0, -1)
: pathWithoutQuery;

return `${defaultUrl}${cleanPath}`;
};

const canonicalUrl = getCanonicalUrl();
const title = pageTitle ? `${pageTitle} • Mrugesh Mohapatra` : defaultTitle;
const description = pageDescription || defaultDescription;
const url = pageUrl || defaultUrl;
const image = pageImage || defaultImage;

return (
Expand All @@ -34,20 +74,20 @@ export const MetaHead: React.FC<MetaHeadProps> = ({
/>
<title>{title}</title>
<meta name='description' content={description} />
<link rel='canonical' href={url} />
<link rel='canonical' href={canonicalUrl} />
<meta name='author' content='Mrugesh Mohapatra' />

{/* Open Graph / Facebook */}
<meta property='og:type' content='website' />
<meta property='og:url' content={url} />
<meta property='og:url' content={canonicalUrl} />
<meta property='og:title' content={title} />
<meta property='og:description' content={description} />
<meta property='og:image' content={image} />
<meta property='og:site_name' content='Mrugesh Mohapatra' />

{/* Twitter */}
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:url' content={url} />
<meta name='twitter:url' content={canonicalUrl} />
<meta name='twitter:title' content={title} />
<meta name='twitter:description' content={description} />
<meta name='twitter:image' content={image} />
Expand Down
2 changes: 1 addition & 1 deletion src/pages/blog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const PageWrapper: React.FC<{
children: React.ReactNode;
}> = ({ children }) => (
<>
<MetaHead pageTitle='Recent posts' />
<MetaHead pageTitle='Recent posts' setCanonicalBlogBaseURL={true} />
<Layout variant='main'>
<section className={cn('mb-8')}>
<div className={cn('prose prose-lg prose-slate max-w-none')}>
Expand Down
6 changes: 5 additions & 1 deletion src/pages/blog/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ const BlogPost = () => {

return (
<>
<MetaHead pageTitle={post.title} />
<MetaHead
pageTitle={post.title}
setCanonicalBlogBaseURL={true}
blogSlug={post.slug}
/>
<Layout variant='prose'>
<article
className={cn(
Expand Down

0 comments on commit 9925429

Please sign in to comment.