kompas.id
3,295 commits across five years and three architectural evolutions to build the digital future of Indonesia’s most trusted news institution.
Stack: WordPress, WooCommerce, Timber/Twig, Nuxt.js, Vue.js, Vuex, Tailwind CSS, Redis, Jest, Storybook
Summary
I led the frontend architecture for kompas.id—Kompas's subscription news platform—through three major phases over five years. The platform converts Indonesia's decision-makers (politicians, executives, professionals) into paying subscribers for premium journalism.
The numbers:
3,295 commits across two codebases (1,228 in WordPress, 2,067 in Nuxt.js)—architectural ownership across multi-year phases, not ticket churn
Lead contributor on both platforms—highest commit count among all developers
Author of record in the Nuxt.js package.json
85% CSS reduction (300KB → 43KB)—directly improving mobile performance on Indonesian networks
Built @kompas/ui—the shared component library used across Kompas products (top contributor with 117 commits)
Phase 1 (2016–2017): The Subscription Foundation
~410 commits. Building the revenue engine from scratch.
When the project kicked off in October 2016, Kompas needed a premium digital platform to complement its print subscriptions—distinct from the free kompas.com and kompas.tv. Building on lessons from kompasprint.com, kompas.id would be the subscription-based home for quality journalism. I was assigned to build the foundation.
What I Built
Modular plugin architecture: I designed the WordPress theme with plugins that could be toggled independently. Editorial teams could enable/disable features without calling a developer. Marketing could A/B test without risking site stability. This wasn't over-engineering—it was a business requirement for a newsroom that needed to experiment fast.
Paywall logic: The system determines who sees what—free articles, premium content, freemium teasers. It handles edge cases that directly affect revenue: expired subscriptions, grace periods, promotional access. Getting these wrong means either giving away content or blocking paying customers.
Auth bar: A header component reflecting real-time membership state for four user types: logged out, logged in non-subscriber, active subscriber, expired subscriber. Each state triggers different UI and different calls-to-action. Updates without page refresh—important when readers open multiple articles in tabs.
Billing emails: I customized the WooCommerce email lifecycle: order confirmations, refund notifications, renewal reminders, failed payment alerts. These touchpoints are where subscribers decide to stay or churn.
Live blogging: Real-time coverage infrastructure for breaking news, including JW Player integration for live video. Updates inject without disrupting readers mid-article, preserving scroll position.
The Reality
Working while on year-end vacation in December 2016. Platform launch with hard deadlines. Real money. Real subscribers. I shipped during holidays because the business couldn't wait.
Phase 2 (2018–2020): Refactoring Under Load
~810 commits. Paying down debt while keeping revenue flowing.
By 2018, the codebase had accumulated debt. Constant design changes and rapid feature development had layered complexity. Every new feature risked regressions. Onboarding new developers meant explaining years of accumulated decisions.
I pushed for refactoring instead of rewriting. Rewrites are expensive gambles. Refactoring lets you ship improvements while keeping the site live and revenue intact.
Architectural Changes
Timber/Twig separation: Rebuilt the PHP layer to separate logic from templates. PHP handles data and business rules; Twig templates handle display. This mirrored Laravel's patterns—enterprise architecture adapted to WordPress. Classes like Kompas\Pages\Meta and Kompas\Helpers\Logger replaced scattered procedural code.
Tailwind migration: The previous stylesheet had grown to ~300KB—years of CSS without deletions. I migrated to Tailwind with PurgeCSS, dropping production CSS to ~43KB. On Indonesian mobile networks—mid-range phones, spotty 3G—that 85% reduction directly affects bounce rates.
Vue.js islands: Introduced Vue not as a full rewrite, but as isolated components for complex interactions: AuthBar.vue, Paywall.vue, VideosPlaylist.vue, PromotedContent.vue. Server-rendered HTML for SEO, Vue components for interactivity.
The Hard Conversation
Convincing stakeholders to invest in refactoring is never easy. "The site works, why change it?" I had to make the case that maintenance costs were rising, that every new feature was getting slower to ship, that new developers were taking too long to onboard. Refactoring doesn't show up in feature announcements, but it shows up in velocity.
Phase 3 (2020–2021): The Nuxt.js Migration
2,067 commits. Complete platform rebuild under production constraints.
By mid-2020, WordPress had become a constraint. The business needed faster iteration and better performance than WordPress could provide. I led the migration to Nuxt.js.
First commit: January 2020. Working prototype: April 2020. Production traffic: by end of 2021.
Architecture
Server-side rendering: Nuxt.js with Vue.js. Search engines crawl content without executing JavaScript. Paywall logic runs server-side before pages reach browsers.
API layer: The frontend consumes multiple backends: APIKID for content, APIGEN for user data, account service for authentication. Express middleware handles routing, auth flows, and response transformation. The frontend talks to APIs, not WordPress.
Redis caching: Static content—category listings, menus, popular posts—cached at the middleware layer.
Vuex state management: Authentication, paywall status, article metadata, user preferences. The state.post.isPaywalled getter determines content visibility for every user on every page.
Testing and Documentation
Unit tests from day one. Jest runs on every commit via GitHub Actions. Tests for auth components, membership alerts, UI components. Testing was part of definition of done.
Storybook documents components in isolation. Developers see how <kid-card-7> or <AppHeaderAuthBar> looks without running the full app.
The Design System Fight
I built @kompas/ui—a shared component library for buttons, inputs, cards, navigation, typography. Getting buy-in to adopt it across Kompas products wasn't automatic. Teams had their own patterns. I had to demonstrate that shared components meant faster development and visual consistency, not imposed constraints.
Shared Content Components
I extracted article rendering into @kompas/submodules: KsmSingleText, KsmSingleImage, KsmSingleBlockquote. Updates propagate to all consuming applications.
SEO
JSON-LD structured data for articles, breadcrumbs, and organization markup. Dynamic sitemaps. For a news site competing for search traffic, SEO isn't optional—it's infrastructure.
What I'd Do Differently
TypeScript from day one. We were weak on TypeScript adoption. By the time I left Kompas, only one project used it. The Nuxt.js codebase is plain JavaScript, and every complex refactor reminded me why static typing matters. If I could redo Phase 3, TypeScript would be non-negotiable.
Impact
Phase 1: Built subscription infrastructure from zero. Paywall logic that determines content access. Shipped under deadline pressure during holidays.
Phase 2: 85% CSS reduction improving mobile performance. Enterprise patterns reducing maintenance costs. Faster onboarding for new developers.
Phase 3: Complete platform rebuild while maintaining production stability. Testing and documentation practices that persist today. Design system and component libraries shared across Kompas products.
Still Running
As of 2026, kompas.id still runs on Nuxt.js. The architecture has evolved since I left in December 2021, but the core SSR approach, caching patterns, and component boundaries remain intact.
Like what you see?
I'm open to freelance projects and full-time roles. If you need someone who obsesses over structure and ships clean code — let's talk.
:quality(70))