I read the same news story framed completely differently in Spanish and British media. Same event, different emphasis, different tone, different conclusions. I wanted a tool that made that visible at a glance.
That became Crosswire.online.
The concept
The idea is dead simple: pick two or more countries, see their top news outlets side by side. No algorithmic curation, no opinion — just the raw homepages of real newspapers from around the world, presented together so you can draw your own conclusions.
The hard part was making it feel effortless.
Why Edge Runtime
Crosswire.online runs on Next.js 15 with Edge Runtime. Every page is rendered at the CDN edge closest to the user, which matters when you are serving a tool meant for global use.
The geolocation feature depends on this. Cloudflare, Vercel, and most CDN providers inject the user’s country code into request headers. The app reads that header server-side and auto-redirects to the user’s local crosswire. No third-party geolocation API, no client-side lookup, no latency penalty.
This also means the app works without a traditional backend. No database, no server process, no cold starts. The entire runtime cost is effectively zero at rest.
The 3D globe problem
I wanted two ways to navigate: a fast text search for people who know what they want, and an interactive 3D globe for people who want to explore.
The globe uses three.js via react-globe.gl. It looks great, but it is heavy — the library alone adds significant weight to the bundle. The solution was aggressive code splitting. The globe component is dynamically imported with next/dynamic and ssr: false, so it only loads when the user switches to interactive mode. The text search is the default, keeping the initial load fast.
Camera transitions were surprisingly fiddly. When a user clicks a country point, the globe needs to rotate smoothly to center that point. The math is straightforward (spherical coordinates to camera position), but getting the easing right — not too slow, not too abrupt, no jitter — took more iterations than I expected.
The proxy challenge
The core feature — displaying outlet homepages in iframes — is technically hostile territory. Most news sites set Content Security Policy headers that prevent framing. Many use X-Frame-Options: DENY.
Crosswire.online solves this with a proxy route at /api/proxy. When a user selects an outlet, the app fetches the homepage server-side, strips the CSP and X-Frame-Options headers, and serves the content through its own domain.
This raises a few challenges:
Character encoding. News sites around the world use different encodings. A Japanese outlet might serve Shift-JIS, a Russian one might use Windows-1251. The proxy detects the charset from HTTP response headers and converts everything to UTF-8 using iconv-lite.
Light mode enforcement. Many sites detect dark mode via prefers-color-scheme. When you are comparing outlets side by side, mixed light/dark themes are visually jarring. The proxy injects CSS to force light mode on all embedded content.
User-Agent handling. Some sites serve different content to different clients. The proxy sends realistic browser headers to get the same experience a normal reader would see.
Curating 376 outlets
Selecting which outlets to include for each country was a manual process. The criteria were: editorial independence, readership size, and political spectrum diversity. For most countries, Crosswire.online includes outlets from across the political spectrum — not to present “both sides” but to show the full range of how a country’s media frames events.
The data is structured as a flat TypeScript file — 82 countries with latitude/longitude coordinates and 376 outlets with their homepage URLs. No API, no database. When an outlet changes its URL or shuts down, it is a one-line code change.
Multi-layout design
Different comparison tasks need different layouts. Comparing two countries works best in two columns. Scanning many outlets quickly works better in rows. Deep-reading a single outlet needs full width.
Crosswire.online supports three layouts — two columns, three columns, and rows — plus a compact spacing toggle. On mobile in portrait orientation, it automatically forces a single column. The layout state is client-side only, no URL state to manage.
The responsive height calculations were the trickiest CSS. Each pane needs to fill available viewport height minus header and controls, and that height changes based on layout mode, spacing, and screen size. CSS custom properties made this manageable.
Favorites without a backend
Users can star their favorite outlets and compare them in a dedicated view at /favorites. The state lives in localStorage — no accounts, no backend, no privacy concerns.
The implementation uses a custom React hook (useFavorites) with hydration-aware loading. On initial render, favorites are unknown (server has no localStorage), so the hook exposes an isLoaded flag. Components show a neutral state until the client hydrates and localStorage is read. This prevents flash-of-wrong-content.
What I learned
Edge-first changes your architecture. When you commit to Edge Runtime, you give up Node.js APIs, long-running processes, and most npm packages. What you gain is global distribution with zero infrastructure management. For a read-heavy, compute-light app like Crosswire.online, the trade-off is overwhelmingly positive.
Code splitting matters more than you think. The three.js globe is the app’s signature visual, but most users navigate via text search. Lazy loading the globe saved roughly 400KB from the critical path.
Manual curation scales surprisingly well. 376 outlets across 82 countries sounds like a lot, but it is a single file that changes infrequently. The temptation to build an admin panel or a scraping pipeline was strong. Resisting it was the right call — the tool shipped months earlier.
Proxy routes are fragile. News sites change their headers, encoding, and framing policies regularly. The proxy needs ongoing maintenance. This is the least glamorous part of the project and the most important.
Crosswire.online is a reminder that sometimes the most useful tools are not the most technically complex — they just put existing information in a new arrangement.
