Skip to content

Commit 36c9433

Browse files
feat: refactor blog routing and URL handling; add middleware for subdomain redirects
1 parent 1608183 commit 36c9433

File tree

5 files changed

+186
-74
lines changed

5 files changed

+186
-74
lines changed

src/Components/UI/BlogCard.tsx

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,20 @@ export default function BlogCard({ post }: BlogCardProps) {
3232
<CardBox classNames="group h-full flex flex-col overflow-hidden hover:bg-zinc-800/10 hover:border-zinc-500 border-zinc-500">
3333
{/* Image */}
3434
{post.image?.asset?.url ? (
35-
<Link href={`/blogs/${post.slug.current}`} className="block">
35+
<Link href={`/${post.slug.current}`} className="block">
3636
<div className="relative w-full h-48 sm:h-56 overflow-hidden cursor-pointer">
3737
{/* Loading Shimmer */}
3838
{imageLoading && !imageError && (
3939
<div className="absolute inset-0 image-shimmer bg-gray-700" />
4040
)}
41-
41+
4242
{/* Image */}
4343
<Image
4444
src={post.image.asset.url}
4545
alt={validateAltText(post.image.asset.altText, getBlogAltText(post.title), 'Blog post image')}
4646
fill
47-
className={`object-cover group-hover:scale-105 transition-transform duration-300 ${
48-
imageLoading ? 'opacity-0' : 'opacity-100'
49-
}`}
47+
className={`object-cover group-hover:scale-105 transition-transform duration-300 ${imageLoading ? 'opacity-0' : 'opacity-100'
48+
}`}
5049
style={{ willChange: imageLoading ? 'opacity' : 'transform' }}
5150
quality={85}
5251
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
@@ -56,19 +55,19 @@ export default function BlogCard({ post }: BlogCardProps) {
5655
setImageError(true);
5756
}}
5857
/>
59-
58+
6059
{/* Error Fallback */}
6160
{imageError && (
6261
<div className="absolute inset-0 bg-gray-800 flex items-center justify-center">
6362
<div className="text-gray-400 text-4xl">📝</div>
6463
</div>
6564
)}
66-
65+
6766
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
6867
</div>
6968
</Link>
7069
) : (
71-
<Link href={`/blogs/${post.slug.current}`} className="block">
70+
<Link href={`/${post.slug.current}`} className="block">
7271
<div className="relative w-full h-48 sm:h-56 overflow-hidden cursor-pointer bg-gray-800 flex items-center justify-center">
7372
<div className="text-gray-400 text-4xl">📝</div>
7473
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
@@ -92,37 +91,37 @@ export default function BlogCard({ post }: BlogCardProps) {
9291
</Row>
9392

9493
{/* Title */}
95-
<Link href={`/blogs/${post.slug.current}`}>
94+
<Link href={`/${post.slug.current}`}>
9695
<h2 className="text-lg sm:text-xl font-semibold text-[var(--textColor)] mb-3 group-hover:text-[var(--primaryColor)] transition-colors line-clamp-2 leading-tight cursor-pointer hover:text-[var(--primaryColor)]">
9796
{post.title}
9897
</h2>
9998
</Link>
10099

101100
{/* Excerpt */}
102-
<p className="text-[var(--textColorLight)] text-sm leading-relaxed mb-4 line-clamp-3">
103-
{(() => {
104-
let excerpt = '';
105-
if (typeof post.body === 'string') {
106-
// Clean markdown: remove headers (#), bold (**), italic (*), code (`), links, etc.
107-
excerpt = post.body
108-
.replace(/^#{1,6}\s+/gm, '') // Remove headers
109-
.replace(/\*\*([^*]+)\*\*/g, '$1') // Remove bold
110-
.replace(/\*([^*]+)\*/g, '$1') // Remove italic
111-
.replace(/`([^`]+)`/g, '$1') // Remove inline code
112-
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Remove links
113-
.replace(/\[|\]|\(|\)|`|#|\*/g, ''); // Remove any remaining markdown special chars
114-
// Clean up extra spaces
115-
excerpt = excerpt.replace(/\s+/g, ' ').trim();
116-
return excerpt.substring(0, 150) + (excerpt.length > 150 ? '...' : '');
117-
}
118-
return post.body?.[0]?.children?.[0]?.text || 'No excerpt available...';
119-
})()}
120-
</p>
101+
<p className="text-[var(--textColorLight)] text-sm leading-relaxed mb-4 line-clamp-3">
102+
{(() => {
103+
let excerpt = '';
104+
if (typeof post.body === 'string') {
105+
// Clean markdown: remove headers (#), bold (**), italic (*), code (`), links, etc.
106+
excerpt = post.body
107+
.replace(/^#{1,6}\s+/gm, '') // Remove headers
108+
.replace(/\*\*([^*]+)\*\*/g, '$1') // Remove bold
109+
.replace(/\*([^*]+)\*/g, '$1') // Remove italic
110+
.replace(/`([^`]+)`/g, '$1') // Remove inline code
111+
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Remove links
112+
.replace(/\[|\]|\(|\)|`|#|\*/g, ''); // Remove any remaining markdown special chars
113+
// Clean up extra spaces
114+
excerpt = excerpt.replace(/\s+/g, ' ').trim();
115+
return excerpt.substring(0, 150) + (excerpt.length > 150 ? '...' : '');
116+
}
117+
return post.body?.[0]?.children?.[0]?.text || 'No excerpt available...';
118+
})()}
119+
</p>
121120
</div>
122121

123122
{/* Read More Link */}
124123
<Link
125-
href={`/blogs/${post.slug.current}`}
124+
href={`/${post.slug.current}`}
126125
className="inline-flex items-center text-[var(--primaryColor)] hover:text-[var(--primaryColor)]/80 font-medium group-hover:translate-x-1 transition-all duration-300 mt-auto text-sm"
127126
>
128127
Read More

src/app/blogs/[slug]/page.tsx

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getTimeSincePublished, getBlogPostBySlug, getAllBlogPostSlugs } from '@
1010
import { getBlogAltText } from '@/utils/imageValidation';
1111
import BlogImageWithLoader from '@/Components/UI/BlogImageWithLoader';
1212
import { getArticleSchema, getBreadcrumbSchema } from '@/utils/structuredData';
13-
import { baseURL } from '@/utils/api';
13+
import { getBlogBaseURL, getBlogPath } from '@/utils/url';
1414

1515
export const revalidate = 0;
1616

@@ -62,7 +62,7 @@ export async function generateMetadata({ params }: BlogPostPageProps): Promise<M
6262
} else {
6363
excerpt = post.body?.[0]?.children?.[0]?.text?.substring(0, 160) || '';
6464
}
65-
65+
6666
const metaDescription = excerpt || `Read about ${post.title}`;
6767

6868
return {
@@ -96,15 +96,15 @@ export async function generateMetadata({ params }: BlogPostPageProps): Promise<M
9696
openGraph: {
9797
title: post.title,
9898
description: excerpt,
99-
url: `${baseURL}/blogs/${post.slug.current}`,
100-
images: post.image?.asset?.url
101-
? [post.image.asset.url]
99+
url: `${getBlogBaseURL()}/${post.slug.current}`,
100+
images: post.image?.asset?.url
101+
? [post.image.asset.url]
102102
: [{
103-
url: `${baseURL}/UtkarshSorathia.webp`,
104-
alt: post.title,
105-
width: 1200,
106-
height: 630,
107-
}],
103+
url: 'https://utkarshsorathia.in/UtkarshSorathia.webp',
104+
alt: post.title,
105+
width: 1200,
106+
height: 630,
107+
}],
108108
type: 'article',
109109
publishedTime: post.publishedAt,
110110
modifiedTime: post._updatedAt,
@@ -116,13 +116,13 @@ export async function generateMetadata({ params }: BlogPostPageProps): Promise<M
116116
card: 'summary_large_image',
117117
title: post.title,
118118
description: excerpt,
119-
images: post.image?.asset?.url
120-
? [post.image.asset.url]
121-
: [`${baseURL}/UtkarshSorathia.webp`],
119+
images: post.image?.asset?.url
120+
? [post.image.asset.url]
121+
: ['https://utkarshsorathia.in/UtkarshSorathia.webp'],
122122
creator: '@utkarshsor03',
123123
},
124124
alternates: {
125-
canonical: `${baseURL}/blogs/${post.slug.current}`,
125+
canonical: `${getBlogBaseURL()}/${post.slug.current}`,
126126
},
127127
};
128128
}
@@ -155,13 +155,13 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
155155
if (block._type === 'block') {
156156
const style = block.style || 'normal';
157157
const listItem = block.listItem;
158-
158+
159159
let text = '';
160-
160+
161161
if (block.children && Array.isArray(block.children)) {
162162
block.children.forEach((child: any) => {
163163
let childText = String(child?.text || '');
164-
164+
165165
// Apply marks from the child
166166
if (child?.marks && Array.isArray(child.marks)) {
167167
// Check for strong
@@ -177,7 +177,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
177177
childText = `\`${childText}\``;
178178
}
179179
}
180-
180+
181181
text += childText;
182182
});
183183
}
@@ -201,23 +201,23 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
201201
};
202202

203203
// Convert body to markdown string if needed
204-
const bodyMarkdown = typeof post.body === 'string'
205-
? post.body
204+
const bodyMarkdown = typeof post.body === 'string'
205+
? post.body
206206
: convertPortableTextToMarkdown(post.body);
207207

208208
// Get image URL for structured data
209-
const imageUrl = post.image?.asset?.url || `${baseURL}/UtkarshSorathia.webp`;
209+
const imageUrl = post.image?.asset?.url || 'https://utkarshsorathia.in/UtkarshSorathia.webp';
210210
const excerpt = typeof post.body === 'string'
211211
? post.body
212-
.replace(/^#{1,6}\s+/gm, '')
213-
.replace(/\*\*([^*]+)\*\*/g, '$1')
214-
.replace(/\*([^*]+)\*/g, '$1')
215-
.replace(/`([^`]+)`/g, '$1')
216-
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
217-
.replace(/\[|\]|\(|\)|`|#|\*/g, '')
218-
.replace(/\s+/g, ' ')
219-
.trim()
220-
.substring(0, 160)
212+
.replace(/^#{1,6}\s+/gm, '')
213+
.replace(/\*\*([^*]+)\*\*/g, '$1')
214+
.replace(/\*([^*]+)\*/g, '$1')
215+
.replace(/`([^`]+)`/g, '$1')
216+
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
217+
.replace(/\[|\]|\(|\)|`|#|\*/g, '')
218+
.replace(/\s+/g, ' ')
219+
.trim()
220+
.substring(0, 160)
221221
: post.body?.[0]?.children?.[0]?.text?.substring(0, 160) || '';
222222

223223
// Generate structured data
@@ -231,9 +231,9 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
231231
);
232232

233233
const breadcrumbSchema = getBreadcrumbSchema([
234-
{ name: 'Home', url: baseURL },
235-
{ name: 'Blog', url: `${baseURL}/blogs` },
236-
{ name: post.title, url: `${baseURL}/blogs/${post.slug.current}` },
234+
{ name: 'Home', url: 'https://utkarshsorathia.in' },
235+
{ name: 'Blog', url: getBlogBaseURL() },
236+
{ name: post.title, url: `${getBlogBaseURL()}/${post.slug.current}` },
237237
]);
238238

239239
return (
@@ -250,15 +250,15 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
250250
__html: JSON.stringify(breadcrumbSchema),
251251
}}
252252
/>
253-
<ResponsiveBox
253+
<ResponsiveBox
254254
classNames="min-h-screen dark:bg-[var(--bgColor)] bg-[var(--bgColor)] dark:bg-grid-white/[0.1] bg-grid-white/[0.1] items-center justify-center lg:px-40"
255255
id="blog-post"
256256
>
257257
<ConstrainedBox classNames="px-4 py-16">
258258
{/* Back Button */}
259259
<div className="mb-6 sm:mb-8">
260260
<Link
261-
href="/blogs"
261+
href="/"
262262
className="inline-flex items-center text-[var(--primaryColor)] hover:text-[var(--primaryColor)]/80 transition-colors text-sm sm:text-base"
263263
>
264264
<ArrowLeft className="w-4 h-4 mr-2" />
@@ -320,7 +320,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
320320
</p>
321321
</div>
322322
<Link
323-
href="/blogs"
323+
href="/"
324324
className="inline-flex items-center px-4 sm:px-6 py-2 sm:py-3 bg-gradient-to-r from-[var(--primaryColor)] to-indigo-600 hover:from-indigo-600 hover:to-[var(--primaryColor)] text-white rounded-[var(--borderRadius)] transition-all duration-300 text-sm sm:text-base whitespace-nowrap"
325325
>
326326
Read More Blogs

src/app/blogs/page.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,32 @@ import ConstrainedBox from "@/Components/core/constrained-box";
77
import SectionTitle from "@/Components/common/SectionTitle";
88

99
import { getBlogListingSchema, getBreadcrumbSchema } from "@/utils/structuredData";
10-
import { baseURL } from "@/utils/api";
10+
import { getBaseURL, getBlogBaseURL } from "@/utils/url";
1111
import BlogsPageClient from "@/app/blogs/BlogsPageClient";
1212

1313
export const revalidate = 0;
1414

15-
export const metadata: Metadata = {
16-
title: "Blog | Utkarsh Sorathia - Full Stack Developer",
17-
description:
18-
"Read my latest thoughts on web development, technology, and programming.",
19-
alternates: { canonical: `${baseURL}/blogs` },
20-
};
15+
export async function generateMetadata(): Promise<Metadata> {
16+
const baseURL = await getBaseURL();
17+
const blogBaseURL = getBlogBaseURL();
2118

19+
return {
20+
title: "Blog | Utkarsh Sorathia - Full Stack Developer",
21+
description:
22+
"Read my latest thoughts on web development, technology, and programming.",
23+
alternates: { canonical: blogBaseURL },
24+
};
25+
}
2226

2327
export default async function BlogsPage() {
2428
const posts = await getAllBlogPosts();
29+
const baseURL = await getBaseURL();
30+
const blogBaseURL = getBlogBaseURL();
2531

2632
const blogListingSchema = getBlogListingSchema();
2733
const breadcrumbSchema = getBreadcrumbSchema([
28-
{ name: "Home", url: baseURL },
29-
{ name: "Blog", url: `${baseURL}/blogs` },
34+
{ name: "Home", url: "https://utkarshsorathia.in" },
35+
{ name: "Blog", url: blogBaseURL },
3036
]);
3137

3238
return (

src/middleware.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { NextResponse } from "next/server";
2+
import type { NextRequest } from "next/server";
3+
4+
export function middleware(req: NextRequest) {
5+
const host = req.headers.get("host") || "";
6+
const pathname = req.nextUrl.pathname;
7+
8+
const isBlogsSubdomain = host === "blogs.utkarshsorathia.in";
9+
const isMainDomain =
10+
host === "utkarshsorathia.in" || host === "www.utkarshsorathia.in";
11+
12+
/**
13+
* 1️⃣ MAIN DOMAIN
14+
* Redirect /blogs → blogs subdomain
15+
*/
16+
if (isMainDomain && pathname.startsWith("/blogs")) {
17+
const slug = pathname.replace("/blogs", "");
18+
return NextResponse.redirect(
19+
new URL(`https://blogs.utkarshsorathia.in${slug}`, req.url),
20+
301
21+
);
22+
}
23+
24+
/**
25+
* 2️⃣ BLOGS SUBDOMAIN
26+
* Rewrite / → /blogs
27+
* Rewrite /slug → /blogs/slug
28+
*/
29+
if (isBlogsSubdomain) {
30+
if (pathname === "/") {
31+
return NextResponse.rewrite(new URL("/blogs", req.url));
32+
}
33+
34+
if (!pathname.startsWith("/blogs")) {
35+
return NextResponse.rewrite(
36+
new URL(`/blogs${pathname}`, req.url)
37+
);
38+
}
39+
}
40+
41+
return NextResponse.next();
42+
}
43+
44+
export const config = {
45+
matcher: ["/:path*"],
46+
};

0 commit comments

Comments
 (0)