Project brief
Highlights
Multi-tenant reader
One route serves every blog on the platform
The article reader lives at /[lang]/article/[blogSlug]/[postSlug]. The blogSlug segment selects which blog's configuration to apply — primary color, name, categories, AdSense publisher ID, and site verification tags all come from the blog record. There is no per-blog deployment, no per-blog codebase, and no per-blog build.
A special slug value ("portfolio") short-circuits the lookup and renders portfolio case study content instead of a database post — the same route handler serves both article reading and the portfolio case study pages without a separate route.
SEO
Four Schema.org blocks and canonical hreflang alternates
Each article page injects BlogPosting (with headline, description, author, publisher, image, wordCount, and timeRequired), Blog (with publisher reference), BreadcrumbList (three-item chain: Yurasis → blog → post), and FAQPage (when the post stores a faqSchema array). All four blocks are rendered as <script type="application/ld+json"> in the server HTML.
hreflang alternates are derived from the post's translationOf and translations fields. canonicalTranslation() picks the authoritative locale-slug pair, preferring the requested language, falling back to the blog's defaultLocale, then ko/en. The canonical URL is always the default-locale version of the post.
Related posts
Same-category recommendations without a recommendation engine
When a post has a category, the reader fetches up to six posts from the same category, excludes the current post, and renders the first four as a related posts grid below the article. When no category exists the query falls back to the post's first tag. No embeddings, no ML model — just a filtered API call at request time.
The related post cards link to the canonical path for each post, respecting the language preference of the reader. If a translation exists for the reader's locale, the card links to that translation.
Analytics
Client-side view tracking that never blocks rendering
View count is incremented by a ViewTracker client component that fires a POST request after hydration using a keepalive fetch. The server renders the article without waiting for the view event — the count the reader sees is viewCount + 1, accounting for the current visit before the API confirms it.
Daily metrics are aggregated separately by a cron job that writes to blog_daily_metrics, keeping the hot path (article render) free from analytics writes. The dashboard reads from the aggregated table, not the live viewCount column.
The article reader is the live public surface of the Yurasis platform. Browse any published blog to see per-blog theming, structured data, related posts, and multilingual routing in action with real AI-generated content.
See demo