← All projects

Jornally

A long-form publishing platform, and what two years of it taught me.

Jornally screenshot
~12,000
Lines of code
2021–2023
Ran
~100
Source files
0
Tests
01

The problem

Writing tools and reading tools want opposite things. A writer needs a rich editor that never loses work; a reader needs pages that load fast, render without JavaScript and can be found by a search engine. Most platforms pick one and make the other someone else’s problem.

02

What I built

A publishing platform on Next.js against a Laravel API: accounts and follows, categories and tags, a threaded comment tree with replies and likes, bookmarks, up and down votes, search, and topic circles that readers join and writers can publish into.

The editor is TipTap with a custom toolbar, image upload and table support, and the reading side renders server-side so an article is a real HTML page before any JavaScript runs.

03

Decisions I'd defend

The session token lives in a cookie rather than only in memory, specifically so server-side rendering can read it. Circle and article pages render on the server for a logged-out visitor and for a search engine, and the same code path personalises them for a signed-in reader by degrading to unauthenticated headers when no token is present. One rendering path, two audiences, no client-side flash of the wrong state.

Drafts autosave on every keystroke and restore on load. A long-form writing tool losing work to an accidental refresh is the failure that loses you the writer, not the session.

Server-side data for a page is fetched in parallel rather than in sequence, so a circle page issues its three calls at once instead of stacking three round trips.

04

What I would do differently

There is no layer over the API. The base URL is interpolated at roughly seventy call sites across the components, and three different HTTP styles coexist, sometimes in the same file. Every one of those call sites rebuilds its own auth header. The single highest-value change would have been one client module with an interceptor, and its absence is why there is no consistent handling of expiry, errors or loading state.

The token is also duplicated into local storage alongside the cookie, which means it is readable by any script on the page. A server-set, http-only cookie was the right answer and I did not reach for it.

There are no tests. The comment and reply tree in particular is the kind of recursive, paginated structure that is miserable to verify by clicking, and it is exactly where a copy-paste bug in a pagination call survived unnoticed.

The premise also drifted. Circles were meant to be how writing found its audience, but the default feed stayed global and the requirement to publish into a circle was commented out rather than resolved. That is a product decision left half-made in code, which is worse than either answer.

05

Where it is now

Shut down. It ran from late 2021 to late 2023 across 228 commits, and the domain has since been taken by an unrelated company, so there is nothing left to link to.

I am including it because the interesting part is not the product. It is that the parts I got right, server-rendered reading and draft safety, were the architectural choices, and the parts I got wrong were all the same mistake: no abstraction at the boundary I crossed most often.