The /[locale]/blog route delivers an MDX-backed blog with featured posts, client-side filtering, and analytics tracking. This page explains the architecture, data flow, and how to extend it safely.
| Layer | File | Responsibility |
|---|---|---|
| Route | src/app/[locale]/(pages)/blog/page.tsx | Sets locale, runs SEO helpers, fetches data (featured posts, paginated list, popular tags) via Promise.all, emits Mixpanel page_view. |
| Featured Posts | src/app/components/blog/featured-posts.tsx | Displays hero cards with CTA + categories. |
| List + Filters | src/app/[locale]/(pages)/blog/blog-list-client.tsx | Client component handling search, tag filtering, sort (recent, popular, most_read, top_rated). |
| Post Card | src/app/components/blog/post-card.tsx | Renders title, excerpt, stats, and link. |
| MDX Rendering | src/app/components/blog/post-content.tsx | Uses ReactMarkdown + rehype-raw/rehype-sanitize, memoized to avoid rerenders. |
| Engagement | post-reactions.tsx, post-share.tsx, post-rating.tsx, post-engagement-bar.tsx | Track GA4/Mixpanel events for reactions, shares, and reading progress. |
src/content/blog (see scripts/convert-data-to-json.ts for JSON generation). Each post includes metadata (slug, tags, stats, etc.).src/app/server/blog/get-posts-list.ts, get-featured-posts.ts, get-popular-tags.ts wrap Supabase queries inside React cache() / unstable_cache for deduplication.getPostsList({ limit, offset }) handles offset pagination; use ?page= query string to navigate pages.getPopularTags) prevents fetching 1000 posts just to compute tags (95% less data).blog-list-client.tsx keeps filters in React state. When the user types or selects a tag, it filters the pre-fetched page of results; sorts by published date, reactions, views, or average rating.
Example snippet:
const [sortBy, setSortBy] = useState<SortOption>('recent');
const filteredPosts = useMemo(() => {
// search + tag + sort logic
}, [initialPosts.posts, searchQuery, selectedTag, sortBy]);trackServerEvent('page_view', { page: 'blog' }) and client-side useClickTracking for filter/search interactions./analytics/events):
blog_post_card_clickblog_filter_changeblog_searchblog_post_reactionblog_post_shareblog_reading_progressEnsure new interactions register via the analytics event registry (src/app/lib/analytics-events.ts).
generateMetadata builds canonical URLs and keywords.<script type="application/ld+json">./[locale]/blog/[slug]) should export revalidate = 3600 and unique metadata for social cards.scripts/convert-data-to-json.ts to output JSON for client components.posts, post_reactions, etc.) accessed via repositories under src/app/lib/repositories.src/app/actions/blog/create-post.ts) follow the ActionResult pattern and protect routes with withAdminAuth.SortOption, add case in blog-list-client.tsx, expose translation key (e.g., Blog.filters.sort.top_rated), and update analytics if needed.getPostsList and adjust BlogListClient to fetch more pages via server actions or use streaming.src/i18n/messages/{locale}.json (Blog.* namespace)./blog/rss.xml (see src/app/blog/rss.xml) already exposes RSS; update when adding metadata fields.src/app/test/unit/components/blog/* ensures cards, filters, and search inputs behave correctly.src/app/test/integration/server/blog covers server queries and cache wrappers.src/app/test/e2e/tests/blog.spec.ts) navigate filters, verify reading progress bars, and capture screenshot baselines./AI/)Keep this page updated whenever the blog gains new filters, data sources, or instrumentation so marketing and engineering teams stay aligned.