HISTORY Wiki
The player wiki of HISTORY, a Metin2 private server: 1,600+ items, monsters, maps, quests and skills in 13 languages, edited in place by its admin.
Recommended- Client
- HISTORY Metin2 server
- My role
- Full-stack engineer & DevOps specialist
- Type
- Wiki and CMS
- Timeline
- Jun 2025 – present
- Built with
- JavaScript
- Next.js
- next-intl
- Material UI
- styled-components
- Node.js
- MongoDB
- Mongoose
- Cloudflare
- Nginx
- Coolify
- Telegram
- Git
- GitHub
Screens15 screens
Screen 1 of 15
The home page, in 13 languagesDesktop · 1 / 15
Select the centre screen to see it full size.
Overview
HISTORY is an old-school Metin2 private server, and its wiki is where players look things up: every weapon and armour with its +0 to +9 upgrade ladder, every monster with its drops and spawn maps, the quests, the dungeons and the skills of all five classes.
I built the wiki and the mini CMS behind it, and I still maintain both. Metin2 wikis are dense in-game panels rather than articles, so every page recreates a panel from the game, and the server's admin edits those same pages in place instead of filling in forms. The wiki serves 1,622 entries in 91 categories, in 13 languages, Arabic included, right to left.
What I delivered
- Wiki pages that recreate the game's own panels: items, monsters, maps, quests, NPCs and skills
- Weapon, armour and jewellery tables with the full +0 to +9 upgrade ladder and upgrade costs
- A mini CMS that edits every page in place, with the same components the visitors see
- A rich-text editor (Tiptap) with tables, column grids, colours and sized images
- 13 languages with a translated URL per page, Arabic right to left
- An instant header search across every item, monster, map and skill, with typo tolerance
- Class skill sections: 5 classes, 63 skills, with animated previews on a CDN
- Every picture that stands for a page links to it, across 3,000+ images
- Fully cached pages, refreshed the moment an admin saves
- Category trees, ordering by drag and drop, and a home-page editor
- One-click backup sent to Telegram, and page errors reported to Telegram
How I built it
One component, two modes
There is no separate admin form. Every layout takes an editing flag and swaps inputs in for text, so the server's admin edits the exact page players read, in place, and a save is checked against that layout's schema before it reaches the database. Twelve layouts cover the whole wiki. When the client asks for something new, the default answer is a per-category option rather than a new layout, so a change reaches only the categories that asked for it.
Pages that render once, then come from cache
Profiling the slowest page showed the database at 0.8% of the time. The real cost was React rendering and styling: one page serialised 11,444 style objects, and only 40 of them were distinct. I moved those styles out of the per-item loops and made every public page fully cached rendered output, with no time limit and purged by tag the moment an admin saves.
| Measured on 33 routes | Before | After |
|---|---|---|
| Total server time | 22,030 ms | 1,804 ms (−92%) |
| Slowest pages | 1.3–3.1 s | 51–165 ms (18–29× faster) |
| Bytes over the wire | 180–459 KB | 54–99 KB (−70 to −78%) |
I checked that nothing visible changed: 31 of the 33 pages came out identical, word for word and link for link, and the other two changed on purpose.
13 languages without 13 copies
Content is written once in Romanian. Each document carries sparse translations of its text and links only, merged at read time (the reader's language, then English, then Romanian), so images, numbers and structure are never duplicated. Every page has its own URL per language. A link in any language redirects to the right one, and pages declare all 13 alternates for search engines.
The translation pipeline turns 30,937 strings into 6,127 units by lifting out links and numbers, so translators never touch a URL, and output whose tags or placeholders don't match is rejected. About 45% of the units come straight from the game's official name tables. Machine-translation-service output is refused outright: once, it had slipped into 3,152 units.
A search that answers in milliseconds
The browser downloads its language's whole index once, about 1,700 entries in roughly 60 KB gzipped, and searches it in memory on every keystroke. Matching ignores accents and tolerates a typo, results are grouped by kind, and an entry with no page of its own lands on its card on the category page.
Rich text that survives the editor
Descriptions moved from Markdown to sanitised HTML edited in Tiptap, with resizable tables, column grids, colours and images sized from the image itself. Scripts migrated the old content, backfilled image sizes and proved the stored HTML round-trips through the editor unchanged.
Every picture is a link
An image links to its page when an anchor sits right next to it in the text, or through a stored link on list entries. 2,340 of the 3,249 images in rich text became links. The anchors generate no box of their own, and I confirmed the rendered pages stayed byte-identical apart from the new links.
Skills, on a CDN
Five classes, nine skill trees and 63 skills got their own in-game skill panel. Their 483 images and animations (91 MB of GIFs) live on Bunny.net and load only when scrolled into view.
Running it
Coolify deploys the app. A one-click backup downloads every item and category as JSON and sends the same file to Telegram, and a crash on an item page reports its stack trace there too.
By the numbers
| Entries | 1,622 items in 91 categories |
| Languages | 13, Arabic right to left |
| Translated strings | 30,937, in 6,127 units |
| Layouts | 12 |
| Server time, 33 routes | 22.0 s → 1.8 s |
| Linked images in rich text | 2,340 |
| Commits | 279 |
