A personal, single-user, full-stack media browser. Reddit-primary, NSFW-focused, mobile-first.
Repository: astrobyte-dev/nightfeed.
Active personal project. Not packaged for distribution. Not intended for shared / multi-user deployment. No Nightfeed user login. Reddit uses backend OAuth and respects upstream rate-limit responses.
- Backend: Node 22+ (ESM), Express,
morgan,cors,dotenv. No database. - Frontend: React 18 + Vite, plain JS,
hls.jsfor HLS playback, dynamicdashjsimport for DASH. State in React hooks + localStorage (+ IndexedDB once feed-mode watch tracking lands). - Tooling: ESLint (flat config), Prettier, Vitest (frontend),
node:test(backend). SeeTESTING.md. - AI assistants:
CLAUDE.mdis the canonical project context for Claude / Copilot. Read it before contributing changes.
| Source | Endpoint prefix | Notes |
|---|---|---|
/api/subreddit, /api/reddit, /api/user |
Primary source. Comments supported in the viewer and feed mode. | |
| Coomer | /api/coomer |
Secondary. Creator-indexed. |
| Eporner | /api/eporner |
Secondary. |
| YouTube | /api/youtube |
Secondary. |
Adding a new source is a future architectural goal — see CLAUDE.md "Source-adapter goal."
- Subreddit browsing with
hot,new,topsorts. - Reddit filters: keyword, flair, include/exclude terms, min score, search scope, NSFW toggle, host filter.
- Multi-source unified grid with source toggle.
- Media viewer with inline video, gallery navigation, comments sheet, and original-source fallback.
- Favorites, blocklist (authors and subreddits), saved subreddits, recently searched.
- Preferences: theme (dark / light / system), density.
- URL state sync — current filter set is reflected in the URL bar.
- Skeleton grids, infinite scroll, debounced search.
- Full-screen feed with windowed media, native RedGIFs playback, sound, pause, favorites, comments, and explicit exit/navigation controls.
- Mobile two-column cards and bottom navigation for Search, Feed, Filters, and Library. Discovery and advanced controls expand when needed.
- Still in progress: Watch-time-aware ranking, extended gestures, PWA install. See
issues/feed-mode-prd.md.
The default local ordering preserves the source's Hot/New/Top ranking. Explicit local ordering remains available in Library > Filters. New favorites retain a media snapshot and reopen inside Nightfeed; older saved records can still open their original links. Advanced filter Reset changes the draft until Apply is pressed. Clear filters removes both Reddit and advanced restrictions.
Source and search URLs survive refresh. Preferences and saves remain browser-local, using the existing storage keys. See the September product review for changes, validation, and remaining work.
.
├── CLAUDE.md, memory.md, TESTING.md # project docs for humans + AI
├── README.md # this file
├── .github/
│ ├── copilot-instructions.md
│ └── workflows/ci.yml
├── issues/ # PRDs + tracer-bullet issues
├── backend/
│ ├── .env.example
│ └── src/{app.js, server.js, routes/, services/, utils/}
└── frontend/
├── .env.example
├── index.html
└── src/{App.jsx, main.jsx, components/, hooks/, utils/, styles.css}
-
Requires Node 22 or newer, matching the existing workspace engine constraint.
-
From the repo root:
npm install -
Create env files:
backend/.envfrombackend/.env.examplefrontend/.envfromfrontend/.env.example
-
Run both packages:
npm run dev -
Open:
- Frontend:
http://localhost:5173 - Backend:
http://localhost:3001
- Frontend:
| Var | Default | Notes |
|---|---|---|
PORT |
3001 |
|
REDDIT_CLIENT_ID |
(none) | Required, issued for an approved Reddit web application. |
REDDIT_CLIENT_SECRET |
(none) | Required, backend only. |
REDDIT_REFRESH_TOKEN |
(none) | Written by the local authorization helper, backend only. |
REDDIT_USER_AGENT |
(none) | Required: web:nightfeed:v1.0.0 (by /u/YOUR_ACTUAL_USERNAME). |
| (others) | See backend/.env.example for the current set. |
Reddit requires explicit approval and registered OAuth. LIVE REDDIT IS NOT YET VERIFIED in this checkout because approved credentials are missing. Follow Reddit access setup to request the correct access, run npm run reddit:connect -w backend, and then npm run reddit:verify -w backend. API hosts are fixed; the former REDDIT_BASE_URL override is no longer used. Authentication does not override Reddit's mature-content restrictions.
| Var | Default | Notes |
|---|---|---|
VITE_API_BASE_URL |
http://localhost:3001 |
|
VITE_HTTPS |
false |
Set true to enable HTTPS dev via vite-plugin-mkcert (required for autoplay-with-sound on mobile Chrome). |
Nightfeed is built mobile-first against a Samsung Galaxy S22 Ultra. To test on a phone against a laptop-hosted backend:
-
Start the LAN-exposed dev server:
npm run dev:lanThis binds the Vite dev server to
0.0.0.0:5173. -
Find your laptop's LAN IP (
hostname -Ion Linux/Mac,ipconfigon Windows). -
On the phone, open
http://<laptop-ip>:5173. -
For HTTPS on your LAN, set
VITE_HTTPS=trueinfrontend/.env, restart, and trust the locally generated certificate on the phone. Autoplay with sound still depends on browser policy and user interaction; the feed provides a Sound control and falls back to muted playback when needed. -
PWA install (once issue 019 ships): Chrome menu -> "Install app" / "Add to Home screen". The home-screen icon launches Nightfeed fullscreen.
| Script | Where | Effect |
|---|---|---|
npm run dev |
root | Backend + frontend, hot reload. |
npm run dev:lan |
root | As above, frontend bound to 0.0.0.0. |
npm run build |
root | Production build of the frontend. |
npm run lint |
root | ESLint across both packages. |
npm test |
root | Test both packages (Vitest + node:test). |
npm run test:e2e |
root | Chromium desktop and Android mobile browser scenarios. |
GET /api/subreddit/:name
Query params: sort, after, limit, includeNsfw, timeRange, keyword, includeTerms, excludeTerms, flair, minScore, onlyRedditHosted, searchScope.
GET /api/reddit/comments?permalink=...&limit=12
Accepts HTTPS Reddit comment permalinks or relative /r/.../comments/... paths. Arbitrary destinations are rejected.
GET /api/user/:username — Reddit user submissions.
GET /api/coomer/..., GET /api/eporner/..., GET /api/youtube/..., GET /api/nsfw/new, GET /api/media/..., GET /api/external/redgifs/:id.
These shift more than the Reddit routes. See the route files for the current contract: backend/src/routes/.
GET /api/health -> { ok: true, service: 'nightfeed-api' }.
- Reddit-hosted video metadata is intentionally minimal (public listing endpoints). The lightbox provides an "Open on Reddit" path for richer playback when the in-app player is preview-only.
- Upstream rate limits are not paginated around by Nightfeed; sustained heavy use will see 429s.
- Source requests have a 15-second timeout. Media proxies bound the initial response wait, honor range requests, and stop on disconnect. API responses are private/no-store, and request logs omit browsing paths and queries.
- Reddit's anonymous access returned network-security 403s. All Reddit calls now require approved backend OAuth. See the Reddit investigation; authenticated live content and NSFW availability remain unverified.
- No persistence beyond localStorage and (for the upcoming feed mode) IndexedDB. State is browser-bound. A future feature ("userstate backup") will mirror it to a JSON file behind the backend; see
issues/userstate-backup-prd.md(currently deferred).
This is a personal project. There's no public contribution flow. If you're working on it (or letting Claude / Copilot work on it), the contract is:
- Read
CLAUDE.md. - Pick an issue from
issues/. - Follow
TESTING.mdfor what "done" means. - Update
memory.mdif anything about state, decisions, or open questions changed.
No license currently. Treat as proprietary until one is added.