Rendering Content & the Kanji Edge Case
Website Rebuild with Laravel
Chapter 7 · Rendering Content & the Kanji Edge Case
Two chapters have already quietly dismantled both of the reasons kanji pages were ever kept separate from the rest of this site's content. This chapter folds them into the unified tree — and finds a genuinely different kind of wrinkle than Django Rebuild 7 hit, living at a completely different layer of the stack.
Both Original Constraints, Already Resolved
Kanji pages were kept separate for two real reasons: a fixed, three-level content hierarchy with no natural slot for a flat grid of individual characters, and no self-contained-fragment convention, since a kanji page is a complete standalone document with its own inline stroke-animation markup, not a plain fragment. Chapter 2's own arbitrary-depth Page model removes the first constraint entirely — japanese/kanji/水 fits exactly as naturally as any deep course path. Chapter 4's own {!! $page->body !!} removes the second — a kanji page's stroke-animation markup can live in body and render through the identical mechanism every other page already uses.
The Resolution: One Row Per Kanji Character
A Genuinely Different Kind of Wrinkle: Storage, Not Validation
Django's own kanji wrinkle was an application-level problem — SlugField's built-in validator rejecting the character outright, before the row could ever save. Laravel's slug column is just a plain string with no such built-in validator, so that exact failure mode doesn't exist here at all. The genuine risk lives one layer deeper: whether the database itself can correctly store a multi-byte character in the first place.
utf8 charset is a limited, 3-byte-max subset of real UTF-8 — genuinely insufficient for some characters, though most common kanji (this one included) happen to fit within its limits anyway. utf8mb4 is the real, complete UTF-8 encoding, and it's already Laravel's own sensible default on a fresh install. The right habit isn't assuming this is a problem — it's confirming config/database.php actually still says utf8mb4, especially on any project connecting to an older or hand-configured MySQL instance where that default might have been overridden.
The Real, Active Risk: Multi-Byte-Unsafe String Functions
水 is encoded as three real bytes in UTF-8, but plain PHP functions like substr(), strlen(), and strtoupper() operate on raw bytes, with no awareness that some "characters" actually span several of them. Slicing at exactly the wrong byte offset produces broken, invalid UTF-8 — a real, well-known PHP trap for any future code that ever needs to truncate or manipulate a kanji title programmatically. The mb_-prefixed functions (mb_substr, mb_strlen, and the rest) exist specifically to operate on actual characters instead.
Stroke-Animation Assets Under Vite
The stroke-order library's own CSS/font/data files move into resources/, processed through Chapter 5's own Vite pipeline just like every other asset — still genuinely self-hosted, still never loaded from a third-party CDN. The one non-negotiable property of the original kanji pages carries forward completely unchanged.
Hands-On Exercises
Name kanji's two original constraints and explain why neither one still applies in this Laravel rebuild, tying each one back to a specific earlier chapter.
📄 View solutionExplain why Laravel's own kanji-related wrinkle is genuinely different in kind from Django's SlugField/allow_unicode issue — what layer of the stack does each one actually live at?
Explain why substr($title, 0, 10) is a real risk for a kanji title while mb_substr($title, 0, 10) is not, referencing how multi-byte UTF-8 characters are actually encoded.
Chapter 7 Quick Reference
- Both original constraints already resolved — Chapter 2's arbitrary-depth model, Chapter 4's
{!! !!}rendering - No
SlugField-style validator in Laravel — theslugcolumn is plain, unvalidated string storage - The real wrinkle lives at the database layer —
utf8mb4vs. the older, limitedutf8charset; already Laravel's own sensible default, worth confirming rather than assuming - The real, active risk: plain PHP string functions (
substr()) operate on bytes, not characters —mb_substr()and the othermb_functions are the safe alternative - Different layer, same underlying fact — Django's wrinkle is application-level validation; Laravel's is database storage plus string-handling discipline
- Stroke-animation assets — moved into Vite's own pipeline, still genuinely self-hosted
- Next chapter: Dynamic Content & Forms