# Salta News — A Digital Newspaper in 41 Days, with a Newsroom of AI Agents

> I rebuilt the Salta News newspaper from scratch: 13,000 articles migrated from WordPress without losing a single URL, an admin built for publishing from a phone, and four AI agents on a VPS that file stories with scoped permissions and source traceability. From first commit to production in 41 days.

- Human version: https://www.augustosalazar.com.ar/project/salta-news-diario-digital-con-redaccion-de-agentes-de-ia
- Author: Augusto Salazar — https://www.augustosalazar.com.ar/llms.txt
- Tags: Full Stack, NextJS, Payload CMS, TypeScript, Supabase, AI Agents, Claude, Vercel, SEO

## The brief

Salta News is a digital newspaper from Salta, Argentina: several stories a day, sharp spikes around crime and court news, and a small, non-technical newsroom that often publishes from a phone. It ran on a WordPress install with years of patches, ads hard-coded into the theme, tools scattered across other domains and an archive more than a decade deep.

The job came with three non-negotiables: **a fixed launch date (October 1, 2026)**, **not losing a single archive URL** — that is where Google traffic comes from — and everything had to be **maintainable by one person** a year from now, with no prior context.

**My role:** architecture, full-stack development, migration, infrastructure and AI agent orchestration. Dev team: one person.  
**Scope:** public site, newsroom admin, reader accounts, PWA, live data integrations and the agent layer.  
**Timeline:** 41 days from first commit to DNS cutover. 177 commits and roughly 38,000 lines of TypeScript one week after launch.  
**Tech stack:** Next.js 16 (App Router) · React 19 · Payload CMS 3 · TypeScript · Supabase PostgreSQL and Storage · Tailwind CSS v4 · Vercel (functions in São Paulo) · Resend · GitHub Actions · Vitest + Playwright · Claude (development and agents) · Paperclip on a Dockerized VPS  
**URL:** [saltanews.com.ar](https://www.saltanews.com.ar)

## 41 days, in phases

I worked from a master prompt split into phases approved one at a time: scaffolding, content model and permissions, migration, templates, SEO, services and ads, analytics. Each phase closed with a summary, and the next one did not open without sign-off. That is what let a project this size ship on time with one person: each day's scope was written down before the day started.

I did not invent the interface: it came from a design made in Claude Design, which I translated into Next.js server components, with no `useEffect` for anything that needs to be indexed.

![Timeline from Aug 21 to Oct 1 with commits per day and the milestones of each phase](https://www.augustosalazar.com.ar/images/saltanews-timeline-en.png)

**Caption:** From scaffolding to DNS cutover. Phases closed one at a time; anything that did not serve the launch waited.

## Architectural decisions

**A small monolith instead of a pile of services.** Next.js and Payload CMS 3 live in the same app: the newsroom admin and the public site share types, database and access rules. One deploy, one place to look when something breaks. For a one-person team, every extra service is one more thing on call.

**The database close to the readers.** Postgres and Storage on Supabase, and Vercel functions in São Paulo, right next to the database. With the default region (Washington) every query made a round trip across the continent, and the homepage took 3.6 to 4.3 seconds to start arriving. Moving them was the cheapest speed win of the project.

**The newspaper as a template.** Name, domain, time zone and locale come from environment variables, not from templates. On top of that I built `diario-base`, a separate repository for launching other newspapers. I evaluated and rejected a multi-tenant SaaS: a single query that forgets to filter by tenant leaks data across clients, and it is a permanent trap for the AI agents that maintain the code.

**Permissions live in the codebase, not in the admin.** Admin, editor, writer, agent and reader roles, defined in a single file and enforced by integration tests. "Anyone with a session" is not the newsroom: a reader with an account also has a session, and that distinction was tested field by field.

![Architecture diagram: Next.js and Payload on Vercel, Supabase, cron triggers, a VPS running Paperclip and external sources](https://www.augustosalazar.com.ar/images/saltanews-arquitectura-en.png)

**Caption:** One app on Vercel, the database on Supabase, and everything that runs on its own comes in through secret-protected cron routes.

## The migration: 13,000 articles and no lost URLs

The import script pulled roughly 13,000 articles from the WordPress API, with their photos, authors, sections and tags, and converted the old HTML into Payload's Lexical editor. Every article keeps its exact URL (`/{slug}` at the root), and the 4,278 tag pages that initially returned 404 were fixed.

The interesting part was at the edges: 1,687 articles pointed to an image that no longer existed, broken links that took down the conversion of an entire article, slugs that Next hands over decoded while the database stores them encoded, filenames S3 would not accept. Each one was solved and written down.

During the transition month, an incremental sync kept pulling whatever the newsroom published on the old site, ads included. On launch day it was switched off and WordPress was retired. The DNS cutover was done with TTLs lowered ahead of time and the rollback values written out.

**Caption:** Same newspaper, same URLs. The whole archive changed platforms without Google losing a page.

## A newsroom of AI agents on a VPS

Beyond the site, I orchestrate story production with **Paperclip**, running in Docker on a 24/7 VPS. There are four agents, all running Claude with a model matched to the role (from Haiku to Opus): a **triage editor** that decides what gets in, a **writer**, a **closing editor** and an agent that coordinates the rest. They work in cycles throughout the day and cover several sections per cycle.

The trigger is deliberately simple: cron-job.org hits a route on the newspaper with a secret, and that route forwards the call to the VPS. The newspaper is the only thing that knows where to call, and switching servers means changing one environment variable.

The part I cared about most was not that the agents write, but **what they can and cannot touch**:

-   **They authenticate with an API key, not a username and password**, and behind the key sits a user with the `agent` role. The key bypasses nothing: it goes through the same access rules as an admin session.
-   **Add versus touch what already exists.** An agent can create articles and upload photos; it cannot edit or delete anything it did not create, not even its own image, and it cannot feature itself on the homepage. If a key leaks tomorrow, the worst case is a few extra articles — never a missing archive.
-   **Publishing is a permission that ships turned off**, and an admin turns it on, agent by agent.
-   **Every article carries its origin.** An internal audit group stores the source URL and outlet, secondary sources, the longest literal run and the overlap percentage with the original. It is closed to the public at the API level (not just hidden in the admin) and pinned by an integration test.

The editorial policy is written down in code and rules too: auto-publishing is not decided by section but by **signals in the text** — naming a private individual as a suspect, stating as fact what is still an allegation, involving a minor, coming from a single source. The next step is a verifier agent whose only job is to report which of those signals appear before a story goes out.

![The four agents on the VPS and the path cron-job.org, newspaper relay, Paperclip and Payload API](https://www.augustosalazar.com.ar/images/saltanews-agentes-en.png)

**Caption:** Four agents with distinct roles and a single door into the newspaper, with the same permissions as a person.

## AI-assisted development, with memory

I wrote the code working with Claude Code, and the rule that paid off most was this: **every bug fixed adds a rule to the repo's AGENTS.md.** The file was not written once; it grew with every incident to almost 1,900 lines — what happened, why, and what not to do again. A Postgres pool capped at one connection that turned out to be a deadlock, an unquoted `#` in `.env` silently truncating a value, a `TRUNCATE ... CASCADE` reaching tables you never named.

That file is what makes the project maintainable by one person — or one agent — a year from now: the context does not live in anyone's head.

![AGENTS.md open on a "When something breaks" entry](https://www.augustosalazar.com.ar/images/saltanews-agents-md.png)

**Caption:** The project's memory: every bug leaves a written, dated rule with its cause.

## Live data that does not go down

Exchange rates, weather, horoscope, a multi-league football fixture with live matches and scorers, a Nasdaq-100 ticker, obituaries and, for Brazil's presidential election, a live head-to-head count with the front-runner shown large.

They all follow the same degradation rule: each module stores its **last good value** in the database, and if the external source goes down, the paper shows that value instead of a gap. It is not a cache: it is the decision that the site never depends on a third party being in a good mood.

![Live modules: Brazil election, football fixture, tech market and exchange-rate ticker](https://www.augustosalazar.com.ar/images/saltanews-vivo.png)

**Caption:** Third-party data, always with a first-party fallback behind it.

## Tools for the newsroom

**Social card generator.** I replaced a tool that lived on a public page on another domain, with no password and the paid keys for three APIs written into the HTML. It now lives inside the admin: keys on the server, role-based access, SSRF protection that checks the resolved IP, and a video tab that replaces the Canva template. The social copy is written by a model on Groq.

**An admin that needs no explaining.** Drafts and published one click away, scheduled publishing every 5 minutes, draft previews, a warning when a photo is too small for Google Discover, a per-person performance dashboard and "view the admin as another person" to give support without asking for passwords.

## Readers with accounts

Sign-up with email or Google, saved stories, reading history, moderated comments, a daily email digest, push notifications through the browser's native prompt, and the paper installable as an app (PWA). When in doubt I always chose the mechanism readers already know from the big newspapers over a custom one: what looks different reads as odd, not as careful.

![Reader sign-up and sign-in on a phone](https://www.augustosalazar.com.ar/images/saltanews-cuenta.png)

**Caption:** Reader sign-up and sign-in, designed phone-first.

## SEO and performance

In launch week I ran a full audit against production and found the most expensive problem: **no page on the site was being served from cache**. An account button in the header read the session on the server, and that silently made the entire newspaper dynamic. I moved the session to the browser and pagination into the route: the site went from responding in 0.5–0.9 s per page to being served from cache in milliseconds.

In the same pass: from 72 image preloads on the homepage to 1, real 404s instead of soft 404s, an honest `lastmod` on 11,379 sitemap entries, a Google News sitemap, RSS, `NewsArticle` structured data, share images WhatsApp actually displays, and football crests down from 128 KB to 1.4 KB.

![Headers before and after: MISS and no-store vs. HIT, with TTFB](https://www.augustosalazar.com.ar/images/saltanews-cache-en.png)

**Caption:** A single `headers()` call in the wrong place made the whole site dynamic. Finding it was worth more than any optimization.

## What was not in the code

Part of the work was diagnosing failures that looked like bugs and were not: GitHub Actions was dropping 21 of every 26 daily cron runs (I measured it and moved the schedule off the busiest minutes), an obituary source that returned 200 with no data from Vercel's servers but worked from a local machine, and deploys the platform left blocked with no visible reason. In every case: prove where the problem is first, touch code second.

## Outcomes

-   Newspaper in production on **October 1, 2026**, on the committed date, after 41 days of development by a single developer.
-   **13,000 articles migrated** with their exact URLs, 4,278 tag pages fixed and WordPress retired.
-   **Four AI agents** producing stories from a VPS, with add-only permissions, publishing enabled by a human and source traceability on every article.
-   Site **served from cache** and ready for Google News: news sitemap, structured data and real 404s.
-   A reusable platform: `diario-base` to launch other newspapers by changing environment variables.
-   A repository that explains itself: integration and end-to-end tests, and an AGENTS.md that grows with every bug.
