The trip becomes a blog
The blog used to be flat text you read once and forgot. The recall mode turns it into the trip's artifact: editable blocks, your own photos, and a PDF you can take with you.
The blog used to be flat text you read once and forgot. The recall mode turns it into the trip’s artifact: editable blocks, your own photos, and a PDF you can take with you.
The context
CamperRoute was born with three modes: plan on the couch, drive in the cabin, recall at home. The first two already had shape. The third was a half-promise: when you got back, the app generated a trip narrative — flat text, day by day, written by an LLM — and that was it.
It was expository without ownership. routes.narratives
stored a flat JSONB {locale: {day: "text"}}: no
paragraphs, no images, no way to regenerate a chunk. The
only possible action was regenerate the whole day. If you
wanted your photos, you opened Google Photos in another
tab. If you wanted it offline, there was nothing to
download. What should have been the memory of the trip was
text you read once.
The decision
Turn the blog into the trip’s final artifact. Three layers, on a base that already existed:
- Blocks, not flat text. The narrative moves to structured content blocks and is edited with a TipTap editor: a slash menu to insert, a floating toolbar, and “regenerate with AI” over the selection — a sentence, a paragraph — not just the whole day.
- Your photos. Direct browser upload to Cloudflare R2 via a signed URL; a worker pulls the WebP variants and strips EXIF. Plus a button to link the entire external gallery (Drive, Photos) without rehosting it.
- A PDF you can take with you. Server-side export: a headless Chromium (Playwright) prints the same page, in portrait or landscape, grouped by day, with maps and images.
The base is the shared knowledge cache: the country context — language, currency, camper regulations, food — that heads the blog with thematic CTAs comes from here, generated once per country and not per user.
Why a shim and not a one-shot migration. There were old narratives in flat JSONB. Instead of migrating them all in a risky batch, a read layer serves them as a single
paragraphblock on the fly; only when you edit the day for the first time does the structured version get written. The migration happens on its own, one day at a time, and never breaks what already worked.
What it’s not
To be explicit:
- It’s not a multi-user CMS or a social network. The blog is yours; it’s public only if you mark the route as public. Then images go through signed URLs and the R2 keys are unguessable UUIDs.
- It’s not automatic re-translation. Once you edit the text, it’s yours: it doesn’t get regenerated in the other languages. The AI proposes; you have the last word.
- It’s not an image generator. The photos are yours or from referenced CC0 sources; we never rehost third-party images.
- It’s not editorial versioning. There’s autosave and local undo, but no persisted version history.
What’s next
The recall mode closes the loop the first post opened: plan, drive, recall — the three modes, now all with shape. And it closes the first series on CamperRoute too: why three modes, why planning and driving are two brains, how POIs surface, how arrival gets detected, what I do with real fuel data, why the code lives in three repos, how I observe without spying, and how the trip ends up becoming a blog.
What I haven’t explained — sprints, branches, BMAD as a method, brand identity, being a solo dev with an AI-assisted SaaS — waits in a second series. When I start it, it’ll link from here and from the product hub. Thanks for reading this far.