Production-oriented global newsroom platform built with Next.js 15, TypeScript, Tailwind CSS, Prisma, PostgreSQL, Redis, REST, GraphQL, and NextAuth.
- Recommended Node version:
20.x - The platform is kept stable on Node 20 for now.
- Some third-party packages may emit future-support warnings under Node 20 during build. Treat those as dependency-maintenance items, not fatal launch blockers.
- Live Prisma-backed article CRUD, autosave, workflow transitions, version restore, comments, assignments, and live updates
- Advanced newsroom editor with preview modes, taxonomy controls, SEO fields, editorial notes, and autosave indicator
- Public article experience with author cards, related stories, comments, bookmarks, sharing, newsletter CTA, and premium lock UI
- Premium homepage with breaking ticker, most-read, premium blocks, opinion/editorial, multimedia, regional sections, personalized reader blocks, and desk sections
- Public search, topic/category/tag/region landing pages, author archives, live coverage pages, pricing page, RSS directory, and subscriber account page
- Protected executive, editor, journalist, media-library, and newsletter dashboards
- Super admin dashboard for user, moderation, audit, and system operations views
- Real newsroom navigation with edition selector, breaking news bar, notification dropdown, and mobile drawer
- Public credibility layers with fact-check labels, source transparency boxes, review status, and correction history
- S3-compatible media storage architecture with presigned upload support and tracked media metadata
- Search adapter architecture with PostgreSQL fallback and filters for category, author, edition, type, and dates
- Subscription plans, payment-provider abstraction, premium article metering, and article locking
- Newsletter segmentation, campaign scheduling, article-linked campaigns, and signup flow
- Breaking news banner management and live blog updates
- Redis-backed job dispatch plus a separate worker process for publishing, newsletters, search indexing, and media processing
- Audit logging hooks, per-route RBAC, loading/error states, RSS feed, sitemap, structured metadata, PWA metadata, Docker, CI, and tests
- AI newsroom layer for research assistance, writing support, content intelligence, and translation workflows
- Global distribution layer for editions, syndication partners, syndication feeds, and licensing
- Video and podcast publishing surfaces for Global Press TV and the podcast network
- Revenue and marketplace primitives for advertisers, campaigns, placements, and premium listings
- Data journalism and global operations models for dashboards, bureaus, crisis coverage, and health monitoring
- CNN/BBC/Reuters-style newsroom navigation with mega menu grouping, sticky breaking bar, edition selector, notification dropdown, and mobile drawer
- Discovery hubs for latest, most read, categories, regions, topics, tags, RSS, and search-backed editorial exploration
- Logged-in reader personalization blocks for continue-reading, recommendations, newsletter preferences, and breaking-alert summaries
- Article trust layers for fact-check labels, review status, source transparency, corrections, why-this-matters framing, and social share cards
- Commercial reader-growth surfaces including a richer media kit, press release intake pricing, referral placeholder, newsletter growth CTAs, and social follow modules
- Demo-safe fallbacks on key public routes so homepage, search, pricing, article, discovery, and media-kit pages still render when
DATABASE_URLis not configured - Seed/live newsroom homepage modes with a real editorial front page, strong imagery, populated desk sections, and free-provider ingestion fallbacks
- Frontend: Next.js 15, TypeScript, Tailwind CSS v4, Framer Motion, shadcn-style UI primitives
- Backend: Node.js, PostgreSQL, Prisma ORM, Redis, REST API, GraphQL Yoga
- Auth: NextAuth, credentials login, Google login, TOTP-ready 2FA flow
- Infra: S3-compatible object storage, Redis queues, Docker Compose, GitHub Actions
cp .env.example .env
docker compose up -d postgres redis minio
npm ci
npm run prisma:generate
npm run db:push
npm run prisma:seed
npm run devOptional worker process:
npm run workerMinIO console: http://localhost:9001
The article image upload flow already uses S3-compatible presigned uploads. Cloudflare R2 works with the existing implementation by pointing the storage env vars at your R2 bucket.
Use this configuration:
S3_ENDPOINT="https://ACCOUNT_ID.r2.cloudflarestorage.com"
S3_REGION="auto"
S3_BUCKET="vanterenpress-media"
S3_ACCESS_KEY_ID="R2_ACCESS_KEY_ID"
S3_SECRET_ACCESS_KEY="R2_SECRET_ACCESS_KEY"
S3_PUBLIC_BASE_URL="https://PUBLIC_R2_URL_OR_CUSTOM_DOMAIN"
S3_FORCE_PATH_STYLE="false"Important details:
S3_ENDPOINTmust be the R2 S3 API endpoint. Presigned uploads are generated against this endpoint.S3_PUBLIC_BASE_URLmust be the public R2 asset URL or your custom domain. Rendered article images use this value instead of the S3 API endpoint./api/rest/healthnow reports storage configuration and bucket reachability so you can verify the R2 connection after deployment.- If any required storage env var is missing, the upload API returns a clear storage configuration error instead of a generic upload failure.
The homepage ingestion layer supports two modes:
NEWS_MODE=seed: use the built-in editorial package and seeded article inventoryNEWS_MODE=live: refresh live homepage news from GNews and NewsAPI on a background cadence, persist the last successful homepage payload, and fall back safely when providers fail or keys are missing
Supported free providers today:
- News: RSS feeds, Guardian Open Platform, GNews, NewsAPI, Currents, TheNewsAPI
- Finance: Alpha Vantage, Finnhub, CoinGecko
- Technology: RSS feeds and public technology/news feeds through the same provider layer
Key environment variables:
NEWS_MODENEWS_REFRESH_INTERVAL_MINUTESNEWS_CACHE_TTL_SECONDSENABLE_RSS_NEWSRSS_WORLD_FEED_URL,RSS_BUSINESS_FEED_URL,RSS_TECH_FEED_URLGUARDIAN_OPEN_PLATFORM_KEYGNEWS_API_KEYNEWS_API_KEYCURRENTS_API_KEYTHENEWSAPI_API_TOKENALPHA_VANTAGE_API_KEYFINNHUB_API_KEYCOINGECKO_API_KEY
If none of the live providers are configured, the homepage and public discovery routes automatically fall back to seeded editorial stories instead of rendering empty sections.
Provider abstraction lives in src/lib/news-providers/:
rss-provider.tsguardian-provider.tsgnews-provider.tsnewsapi-provider.tscurrents-provider.tsthe-news-provider.tsfinance-provider.tsindex.tscache.tsseed-content.ts
How it works:
- Each provider implements the shared
NewsProviderinterface fromsrc/lib/news-providers/types.ts getHomepageNewsResponse()composes provider output into homepage sections and returns the latest homepage payload plus refresh metadata- Live provider output is normalized into the same
EditorialStorymodel used by seed content - Section population always uses seeded stories as fallback, so
Latest News,World,Business,Technology,Sports,Live Coverage,Opinion, andMost Readnever come back empty - A background refresh job runs every
NEWS_REFRESH_INTERVAL_MINUTES, stores the last successful homepage payload in theSiteSettingtable, and preserves the previous successful payload if GNews or NewsAPI fail - The homepage browser client polls
/api/rest/news/homepageon the same cadence and shows aNew stories availablerefresh notice when a newer payload is available - Provider status, last successful refresh, next scheduled refresh, active
NEWS_MODE, and provider errors are returned by the homepage API for debugging
Adding a new provider:
- Create a new file under
src/lib/news-providers/ - Implement
name,isConfigured(), andfetchLatest() - Return normalized
EditorialStory[] - Register the provider in
src/lib/news-providers/index.ts - Add any new environment variables to
.env.exampleand this README
Fallback behavior:
- Missing API keys: provider is skipped
- Provider fetch failure: the previous successful persisted homepage payload is kept
- No live provider data at all: the full seed homepage bundle is returned
- Public discovery and article/live routes also use seeded editorial fallbacks when database content is unavailable
To use live news:
- Open
.env.local - Add:
NEWS_MODE=live
GNEWS_API_KEY=your_gnews_key_here
NEWS_API_KEY=your_newsapi_key_here
NEWS_REFRESH_INTERVAL_MINUTES=15- Restart the dev server:
npm run devNEWS_MODE=seed keeps the homepage on seeded editorial content only. NEWS_MODE=live tries GNews and NewsAPI first, caches the result, and falls back to seed content if keys are missing or provider requests fail.
NEWS_REFRESH_INTERVAL_MINUTES now controls both:
- The background live-news refresh cadence on the server
- The lightweight homepage polling interval in the browser
Open homepage tabs do not need a full reload. They check /api/rest/news/homepage on the configured interval and can apply new stories in place when a newer payload exists.
Homepage API:
GET /api/rest/news/homepage- Returns:
bundlelastUpdatedsource: seed | live | cachedproviderStatusfor GNews and NewsAPI- refresh/debug status including
lastSuccessfulLiveRefresh,nextScheduledRefresh,providerErrors, activeNEWS_MODE, and cache TTL
Add this to .env.local:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"For a local Docker/Postgres setup, a typical value looks like:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/vanterenpress?schema=public"Then run:
npm run prisma:generate
npm run prisma:migrate
npm run db:seed
npm run devadmin@globalpress.network/Password123!eic@globalpress.network/Password123!journalist@globalpress.network/Password123!subscriber@globalpress.network/Password123!
The credentials provider signs in with email plus password. username is not a login field in the current Prisma schema.
Seed the database:
npm run db:seedLocal VANTERENPRESS admin:
- Email:
admin@vanterenpress.com - Display name:
admin - Password:
Chukwuemeka2019$ - Role:
SUPER_ADMIN
Notes:
- The seed is idempotent and updates the local admin if it already exists.
- The default VANTERENPRESS admin is only seeded outside production by default. To allow it in production intentionally, set
ALLOW_DEFAULT_ADMIN_SEED=truebefore seeding. - Change this password before any production deployment by updating the user record after seeding or by disabling the default admin seed and creating a production-only admin account with your own secret.
/live homepage with breaking banners and published article feed/articles/[slug]live article page with premium locking and live updates/latest,/most-read,/topics,/categories,/regions,/tags,/rsspublic discovery routes/dashboardexecutive dashboard/dashboard/editoreditor workflow dashboard/dashboard/journalistjournalist workspace/dashboard/adminsuper admin operations/dashboard/aiAI newsroom workbench/dashboard/distributionregional editions and syndication controls/dashboard/revenueadvertising, subscriptions, and marketplace overview/dashboard/operationsbureaus, crises, health, and disaster recovery/dashboard/datadata journalism projects and visualizations/dashboard/mediamedia library dashboard/dashboard/newslettersnewsletter operations dashboard/authors/[id]journalist/editor profile archive/live/[slug]live coverage page/searchpublic search experience/pricingpricing page/account/subscriptionsubscriber account page/account/notifications,/account/newsletters,/account/saved,/account/following,/account/history,/account/settingsreader account surfaces/distributionpublic syndication and editions overview/videoGlobal Press TV/podcastspodcast network/advertiseadvertiser and sponsorship overview/about,/contact,/careers,/ethics,/editorial-standards,/corrections,/privacy,/terms,/press-releases,/subscribepublic trust pages/statuspublic uptime and health page/loginauthentication entry
/api/rest/articles/api/rest/articles/[id]/api/rest/articles/[id]/autosave/api/rest/articles/[id]/workflow/api/rest/articles/[id]/versions/api/rest/articles/[id]/comments/api/rest/articles/[id]/live-updates/api/rest/assignments/api/rest/media/api/rest/media/presign/api/rest/newsletter/signup/api/rest/newsletters/api/rest/breaking-news/api/rest/search/api/rest/search/suggestions/api/rest/subscriptions/plans/api/rest/subscriptions/checkout/api/rest/articles/[id]/bookmark/api/rest/articles/[id]/public-comments/api/rest/authors/[id]/follow/api/rest/ai/research/api/rest/ai/writing/api/rest/ai/intelligence/api/rest/distribution/partners/api/rest/distribution/feeds/api/rest/video/api/rest/podcasts/api/rest/revenue/ads/api/rest/revenue/marketplace/api/rest/operations/bureaus/api/rest/operations/crisis/api/rest/operations/health
APP_URL,NEXTAUTH_URL,NEXTAUTH_SECRETSITE_NAME,SITE_SUPPORT_EMAIL,DEFAULT_LOCALE,LOG_LEVELDATABASE_URL,DIRECT_URLNEWS_MODE,NEWS_REFRESH_INTERVAL_MINUTES,NEWS_CACHE_TTL_SECONDS,ENABLE_RSS_NEWSRSS_WORLD_FEED_URL,RSS_BUSINESS_FEED_URL,RSS_TECH_FEED_URLGUARDIAN_OPEN_PLATFORM_KEY,GNEWS_API_KEY,NEWS_API_KEY,CURRENTS_API_KEY,THENEWSAPI_API_TOKENALPHA_VANTAGE_API_KEY,FINNHUB_API_KEY,COINGECKO_API_KEYREDIS_URL,UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKENGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETS3_ENDPOINT,S3_REGION,S3_BUCKET,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEYS3_PUBLIC_BASE_URL,S3_FORCE_PATH_STYLE,CDN_BASE_URLEMAIL_PROVIDER,SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASSWORD,SMTP_FROM,RESEND_API_KEYPAYMENT_PROVIDER,STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WEBHOOK_SECRETFREE_ARTICLE_LIMIT,PAYWALL_PREMIUM_LABELAI_PROVIDER,OPENAI_API_KEYSEARCH_PROVIDER,MEILISEARCH_URL,MEILISEARCH_MASTER_KEY,TYPESENSE_HOST,TYPESENSE_API_KEY,ELASTICSEARCH_URLPOSTHOG_KEY,POSTHOG_HOST,SENTRY_DSN,ENABLE_SENTRY
npm run prisma:generatenpm run prisma:migratenpm run db:pushnpm run prisma:seednpm run db:seednpm run workernpm run lintnpm run testnpm run test:e2enpm run build
The following commands were run successfully against the current codebase:
npm run prisma:generatenpm run lintnpm run testnpm run test:e2enpm run build
docs/api-architecture.mddocs/folder-structure.mddocs/ui-wireframes.mddocs/deployment.mddocs/media-storage.mddocs/background-workers.mddocs/ai-newsroom.mddocs/global-distribution.mddocs/video-network.mddocs/revenue-platform.mddocs/data-journalism.mddocs/global-operations.mddocs/platform-scale.mddocs/launch-checklist.mddocs/demo-script.mddocs/accessibility-notes.mddocs/public-discovery.md
The codebase now covers a much fuller publishing experience, but a real launch would still want production billing, object-storage lifecycle rules, richer collaborative presence, external search integration, distributed rate limiting, and formal observability.