This post is a copy of an article I published on our company blog.




There used to be a cartoon called Montana Jones. If I remember right, it aired around the mid‑1990s. In each episode, an anthropomorphized lion and tiger explored ruins and ran into villains led by someone called Zero King. Zero King rode a robot built by his subordinate, Dr. Nitro, blocked the heroes’ path, and after a long chase the good guys always won. The banter between Zero King and Dr. Nitro was the best part.

Zero King: Dr. Nitro, care to explain yourself?
Dr. Nitro: If you could just give me a little more time and budget…
Zero King: Don’t you know excuses are a sin!

I dredged Montana Jones out of the memory box because, building a new service, I oddly sympathized with Dr. Nitro. I suspect a lot of developers at startups would nod along.

If only we had a bit more time! More budget! More people!


The biggest problem was time

Ah, time. The business value of a content platform comes down to paying members and content quality. That makes grabbing the early market hugely important. Users are already used to the UX of existing platforms; without a strong reason, they won’t switch. Content providers also prefer platforms with market pull, so for a newborn platform, securing content takes serious effort.

When Ridistory development started, many platforms already had a firm foothold. Ridistory had to ship faster and differently than anyone else—David taking on Goliath.

This isn’t unique to Ridistory. Many startups lean on managed services for foundations and productive languages and frameworks to get to market quickly—that seems to be the trend.


A web app—and faster

So it was a web app. Four months to launch; options were limited. Early on we only had two server engineers. Even if someone on the team had native app experience, a fast launch meant we had no real choice but the web. Maintaining two separate codebases for Android and iOS and chasing platform‑specific issues wasn’t efficient for time or headcount.

Once the web‑app direction was set, the Ridistory team worked in the way we knew and could ship fastest. We built UI in HTML and used Jinja2 templates from Django for the parts that changed dynamically—a fairly traditional setup. When the shape of the service was roughly in place, we finally voiced the anxiety we’d been carrying inside.

This feels really slow, doesn’t it?

Yes. It was slow—inevitably so. Installed as an “app” on a phone, users expect Ridistory to feel as responsive as other native apps. But Ridistory was implemented like the web, so it felt subtly sluggish. Every navigation flashed white for a moment; interactions felt vaguely constrained.

We stopped everything, piled into a meeting room, and hashed out a plan. After long debate we reached for the scary option: throw away the old approach and move wholesale to a Single Page Application (SPA). As tooling we compared React and Angular, chose React for the gentler learning curve and more flexible style, paired it with React Router, and marched into what you might call a hardship tour.

The task board filled with tickets named after screens. Developers picked them up one by one and migrated Jinja2 screens to React. It wasn’t a simple port: we componentized each screen for reuse and wired scenarios into React’s lifecycle.

git commit graph

A staggering amount of code was deleted and added.

We should have used Redux from the start

Most of our technical pain came from the fate of a web app that has to pretend it isn’t quite web. The first real pain of the SPA migration showed up on the comments screen. From the comment list, users go to a composer to write a new comment. After posting successfully and returning to the list, they don’t see the comment they just wrote—the list still reflects the data fetched before they composed. That’s a hard UX to accept.

To fix it we started adding client‑side state: we stored the fetched comment list, and when a new comment was sent to the server we also appended it locally. Returning to the list then rendered from that store, so the new comment appeared. Beyond comments, many kinds of data had to live on the client—for example, the signed‑in user was shared across screens, so fetching it from the server every time was wasteful.

There was another issue. In React you split the UI into components and compose them. On complex screens, A wraps B wraps C, and depth grows. To pass data from A down to C you thread it through B as props. The deeper the tree, the worse it gets. We gathered in the meeting room again.

We should have just used Redux.

We had considered Redux when adopting React but ruled it out as over‑engineering at the time—only to realize later that was wrong. Client state and prop drilling were both problems Redux cleaned up nicely. We regretted it briefly, then adopted Redux quickly. We also pulled in libraries that play well with Redux—Redux Thunk, Reselect, and others—which helped keep the frontend code tidier.


Enter Entity

Besides user info, other data was shared across many screens: metadata for works—primary key (pk), title, author, and so on. Every navigation duplicated that payload from the server. For example:

  • On the lucky box screen, you win a pass for a work → the server sends work metadata to show the title.

  • You tap through to the work detail screen → the server sends work metadata again for the details.

  • You open the reader → the server sends work metadata yet again for the title.

Even setting aside duplicated client memory, we couldn’t accept burning traffic and slowing loads on every round trip.

To clear that debt we treated work metadata specially. We introduced an Entity concept in the Redux store to hold and manage it.

APIs that used to embed full metadata now return only pks. The client requests unknown pks from a dedicated metadata API. Fetched metadata lives in Entity and is reused across screens.

Entity diagram

How Entity works.

After the change, response payloads shrank noticeably and screens drew faster. Maybe only tens of milliseconds—but grains of sand make a mountain.


Web‑like app, app‑like web—and what’s next?

Ridistory is a web app. One codebase covers every platform; developer ergonomics are good; fixes and features ship to production quickly.

There are downsides: compared with native, it’s slower and the UX is weaker. The team worked hard to feel “native,” especially on performance—Redux to keep data fresh across screens, Entity to cut duplication and payload size, even embedding a custom WebView in the app for low‑end Android devices. We also improved perceived speed through UX. Still, optimization has limits, and tuning took longer than expected.

We still hear “Should we go native now?” or “What about React Native?” Shipping fast with a small team while chasing high usability is genuinely hard—but solving those technical problems is rewarding in its own way.