Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 'Modern Tech Stack 2025: What Developers Actually Use'
date: '2025-11-09'
description: 'Discover which programming languages, databases, AI tools, and frameworks developers actually use in 2025 based on the largest developer survey.'
author: 'CoderScreen Team'
author: 'Kuba Rogut'
tags: ['Tech-Stack', 'Developer-Tools', 'Programming-Languages', 'AI-Tools', 'Database-Trends', '2025-Trends']
image: '/blog/modern-tech-stack-2025.png'
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 'AI Engineer Hiring in 2025: The AI-Native Revolution'
date: '2025-11-09'
description: 'How AI is reshaping technical hiring in 2025—from the comeback of junior developers to the essential attributes of next-gen engineers.'
author: 'CoderScreen Team'
author: 'Kuba Rogut'
tags: ['AI-Engineering', 'Technical-Hiring', 'Junior-Developers', 'Coding-Assistants', '2025-Trends']
image: '/blog/state-of-ai-engineer-hiring.png'
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 'Technical Interviews 2025: Navigating the AI Revolution'
date: '2025-11-03'
description: 'How AI is reshaping technical interviews, from AI-assisted assessments to new skill requirements, and what it means for hiring teams.'
author: 'CoderScreen Team'
author: 'Kuba Rogut'
tags: ['Technical-Interviews', 'AI', 'Hiring', 'Coding Assessments', '2025 Trends']
image: '/blog/the-state-of-technical-interviews.png'
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 'Tech Layoffs 2025: Amazon''s AI-Driven Workforce Shift'
date: '2025-11-03'
description: 'Analysis of the 2025 tech layoffs including Amazon''s 14,000 job cuts and how AI is reshaping the workforce across Microsoft, Meta, Google, and Intel.'
author: 'CoderScreen Team'
author: 'Kuba Rogut'
tags: ['Tech-Layoffs', 'Amazon', 'AI', 'Workforce', '2025 Trends', 'Tech Industry']
image: '/blog/amazon-layoffs.png'
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 'Interview Best Practices: How to Judge Coding Ability'
date: '2025-11-04'
description: 'A guide to evaluating developer coding skills through technical interviews. Learn proven assessment methods and best practices to identify top talent.'
author: 'CoderScreen Team'
author: 'Kuba Rogut'
tags: ['Technical-Interviews', 'Coding-Assessment', 'Hiring', 'Developer-Skills', 'Best-Practices']
image: '/blog/best-practice-for-technical-interviews.png'
---
Expand Down
Binary file added apps/marketing/public/blog/author/kuba_rogut.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
132 changes: 90 additions & 42 deletions apps/marketing/src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,44 @@ import { getAllBlogSlugs, getBlogPost, getRelatedPosts } from '@/lib/blog';
import { buildBreadcrumbSchema } from '@/lib/breadcrumbs';
import 'highlight.js/styles/github-dark.css';

const AUTHOR_AVATARS: Record<string, string> = {
'Kuba Rogut': '/blog/author/kuba_rogut.png',
};

function AuthorAvatar({ name }: { name: string }) {
const avatar = AUTHOR_AVATARS[name];

if (avatar) {
return (
<Image
src={avatar}
alt={name}
width={44}
height={44}
className='h-11 w-11 flex-shrink-0 rounded-full object-cover'
/>
);
}

const initials = name
.split(' ')
.slice(0, 2)
.map((word) => word[0])
.join('')
.toUpperCase();

return (
<span className='flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-sm font-semibold text-white'>
{initials}
</span>
);
}

function getReadingTime(content: string): number {
const words = content.trim().split(/\s+/).length;
return Math.max(1, Math.ceil(words / 200));
}

function RelatedArticles({ slug, tags }: { slug: string; tags?: string[] }) {
const related = getRelatedPosts(slug, tags, 3);

Expand Down Expand Up @@ -66,7 +104,12 @@ export async function generateMetadata({ params }: BlogPostPageProps) {

const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://coderscreen.com';
const postUrl = `${siteUrl}/blog/${slug}`;
const imageUrl = post.image || `${siteUrl}/og-image.png`;
// Use the post's thumbnail as the OpenGraph/Twitter image (absolute URL for scrapers).
const imageUrl = post.image
? post.image.startsWith('http')
? post.image
: `${siteUrl}${post.image}`
: `${siteUrl}/og-image.png`;

// Generate keywords from tags and extract key terms from title
const keywords = [
Expand All @@ -82,8 +125,8 @@ export async function generateMetadata({ params }: BlogPostPageProps) {
title: `${post.title} | CoderScreen`,
description: post.description || post.title,
keywords,
authors: [{ name: post.author || 'CoderScreen Team' }],
creator: post.author || 'CoderScreen Team',
authors: [{ name: post.author || 'Kuba Rogut' }],
creator: post.author || 'Kuba Rogut',
publisher: 'CoderScreen',

// Open Graph metadata for social sharing
Expand All @@ -103,7 +146,7 @@ export async function generateMetadata({ params }: BlogPostPageProps) {
],
publishedTime: post.date,
modifiedTime: post.updatedAt || post.date,
authors: [post.author || 'CoderScreen Team'],
authors: [post.author || 'Kuba Rogut'],
tags: post.tags || [],
},

Expand Down Expand Up @@ -139,7 +182,7 @@ export async function generateMetadata({ params }: BlogPostPageProps) {
other: {
'article:published_time': post.date,
'article:modified_time': post.updatedAt || post.date,
'article:author': post.author || 'CoderScreen Team',
'article:author': post.author || 'Kuba Rogut',
'article:section': 'Technology',
'article:tag': post.tags?.join(', ') || '',
},
Expand All @@ -156,7 +199,14 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {

const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://coderscreen.com';
const postUrl = `${siteUrl}/blog/${slug}`;
const imageUrl = post.image || `${siteUrl}/og-image.png`;
// Use the post's thumbnail as the OpenGraph/Twitter image (absolute URL for scrapers).
const imageUrl = post.image
? post.image.startsWith('http')
? post.image
: `${siteUrl}${post.image}`
: `${siteUrl}/og-image.png`;
const author = post.author || 'Kuba Rogut';
const readingTime = getReadingTime(post.content);

const breadcrumbSchema = buildBreadcrumbSchema([
{ name: 'Home', href: '/' },
Expand All @@ -174,7 +224,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
dateModified: post.updatedAt || post.date,
author: {
'@type': 'Person',
name: post.author || 'CoderScreen Team',
name: post.author || 'Kuba Rogut',
},
publisher: {
'@type': 'Organization',
Expand Down Expand Up @@ -225,43 +275,25 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
Back to Blog
</Link>

<header className='mb-8'>
<h1 className='text-5xl font-bold text-gray-900 mb-4'>{post.title}</h1>
<div className='flex items-center gap-4 text-gray-600'>
<time dateTime={post.date}>{dayjs(post.date).format('MMMM D, YYYY')}</time>
{post.author && (
<>
<span>•</span>
<span>{post.author}</span>
</>
)}
</div>
{post.tags && post.tags.length > 0 && (
<div className='flex flex-wrap gap-2 mt-4'>
{post.tags.map((tag) => (
<span
key={tag}
className='inline-block bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm'
>
{tag}
</span>
))}
</div>
<header className='mb-10'>
<h1 className='text-4xl sm:text-5xl font-bold tracking-tight leading-tight text-gray-900'>
{post.title}
</h1>
{post.description && (
<p className='mt-5 text-xl leading-8 text-gray-600'>{post.description}</p>
)}
</header>

{post.image && (
<div className='mb-12 overflow-hidden rounded-lg'>
<Image
src={post.image}
alt={post.title}
width={1200}
height={630}
className='w-full h-auto object-cover'
priority
/>
<div className='mt-8 flex items-center gap-3'>
<AuthorAvatar name={author} />
<div className='text-sm'>
<p className='font-semibold text-gray-900'>{author}</p>
<div className='flex items-center gap-2 text-gray-500'>
<time dateTime={post.date}>{dayjs(post.date).format('MMMM D, YYYY')}</time>
<span aria-hidden='true'>·</span>
<span>{readingTime} min read</span>
</div>
</div>
</div>
)}
</header>

<div className='prose prose-lg max-w-none'>
<MDXRemote
Expand All @@ -275,6 +307,22 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
}}
/>
</div>

{post.tags && post.tags.length > 0 && (
<footer className='mt-12 border-t border-gray-200 pt-8'>
<h2 className='mb-3 text-sm font-semibold text-gray-900'>Topics</h2>
<div className='flex flex-wrap gap-2'>
{post.tags.map((tag) => (
<span
key={tag}
className='inline-flex items-center rounded-md border border-gray-200 bg-gray-50 px-2.5 py-1 text-xs font-medium text-gray-600'
>
{tag}
</span>
))}
</div>
</footer>
)}
</article>

<RelatedArticles slug={slug} tags={post.tags} />
Expand Down
113 changes: 58 additions & 55 deletions apps/marketing/src/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { RiArrowRightLine } from '@remixicon/react';
import dayjs from 'dayjs';
import Image from 'next/image';
import Link from 'next/link';
import { BlogPostGrid } from '@/components/blog/BlogPostGrid';
import { getBlogPosts } from '@/lib/blog';
import { buildBreadcrumbSchema } from '@/lib/breadcrumbs';

Expand All @@ -18,6 +20,7 @@ export const metadata = {

export default function BlogPage() {
const posts = getBlogPosts();
const [featured, ...rest] = posts;

const breadcrumbSchema = buildBreadcrumbSchema([
{ name: 'Home', href: '/' },
Expand All @@ -31,67 +34,67 @@ export default function BlogPage() {
// biome-ignore lint/security/noDangerouslySetInnerHtml: needed for SEO schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/>
<div className='max-w-4xl mx-auto px-4 py-16'>
<h1 className='text-5xl font-bold text-gray-900 mb-4'>Blog</h1>
<p className='text-xl text-gray-600 mb-12'>
Insights on coding interviews, technical assessments, and engineering best practices.
</p>

<div className='max-w-5xl mx-auto px-4 sm:px-6 py-16'>
{/* Header */}
<header className='mb-12 border-b border-gray-200 pb-8'>
<h1 className='text-4xl sm:text-5xl font-bold tracking-tight text-gray-900'>Blog</h1>
<p className='mt-3 max-w-2xl text-lg text-gray-600'>
Deep dives on how we build CoderScreen, plus what we&apos;re learning about coding
interviews and technical hiring.
</p>
</header>

{posts.length === 0 ? (
<div className='text-center py-12'>
<p className='text-gray-500 text-lg'>No blog posts yet. Check back soon!</p>
<div className='py-16 text-center'>
<p className='text-lg text-gray-500'>No posts yet. Check back soon.</p>
</div>
) : (
<div className='space-y-12'>
{posts.map((post) => (
<article key={post.slug} className='border-b border-gray-200 pb-12 last:border-b-0'>
<Link
href={`/blog/${post.slug}`}
className='group block hover:opacity-90 transition-opacity'
>
{post.image && (
<div className='mb-6 overflow-hidden rounded-lg'>
<Image
src={post.image}
alt={post.title}
width={800}
height={450}
className='w-full h-auto object-cover'
priority={posts.indexOf(post) === 0}
/>
</div>
)}
<h2 className='text-3xl font-bold text-gray-900 mb-2 group-hover:text-blue-600 transition-colors'>
{post.title}
</h2>
<div className='flex items-center gap-4 text-sm text-gray-600 mb-3'>
<time dateTime={post.date}>{dayjs(post.date).format('MMMM D, YYYY')}</time>
{post.author && (
<>
<span>•</span>
<span>{post.author}</span>
</>
)}
<>
{/* Featured post */}
{featured && (
<Link href={`/blog/${featured.slug}`} className='group mb-16 block'>
{featured.image && (
<div className='mb-6 aspect-[2/1] w-full overflow-hidden rounded-xl border border-gray-200 bg-gray-100'>
<Image
src={featured.image}
alt={featured.title}
width={1200}
height={600}
priority
className='h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]'
/>
</div>
{post.description && (
<p className='text-gray-700 text-lg leading-relaxed mb-3'>{post.description}</p>
)}
{post.tags && post.tags.length > 0 && (
<div className='flex flex-wrap gap-2'>
{post.tags.map((tag) => (
<span
key={tag}
className='inline-block bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm'
>
{tag}
</span>
))}
</div>
)}
<div className='mb-2 flex items-center gap-2 text-sm text-gray-500'>
<time dateTime={featured.date}>
{dayjs(featured.date).format('MMMM D, YYYY')}
</time>
{featured.author && (
<>
<span aria-hidden='true'>·</span>
<span>{featured.author}</span>
</>
)}
</Link>
</article>
))}
</div>
</div>
<h2 className='mb-3 text-2xl sm:text-3xl font-bold tracking-tight text-gray-900 transition-colors group-hover:text-blue-600'>
{featured.title}
</h2>
{featured.description && (
<p className='max-w-3xl text-lg leading-relaxed text-gray-600'>
{featured.description}
</p>
)}
<span className='mt-4 inline-flex items-center gap-1.5 text-sm font-semibold text-blue-600'>
Read article
<RiArrowRightLine className='size-4 transition-transform group-hover:translate-x-0.5' />
</span>
</Link>
)}

{/* More articles — infinite scroll + "Load more" fallback */}
{rest.length > 0 && <BlogPostGrid posts={rest} />}
</>
)}
</div>
</div>
Expand Down
Loading
Loading