A lot of my curation work for Quora starts on Reddit. Reddit is a great place to find things and a poor place to find them again. Native search ranks by its own idea of relevance, can't combine a date window with a score threshold and a specific author, and older threads can be hard to reach. Sometimes the thread I remembered had also been removed by moderators, or the post had been edited until it no longer said what I came for.
reddit-core is my answer to that. It's a single-page React app that queries the PullPush archive, a community-run index of public Reddit posts and comments, with a proper filter panel. Because the archive captured content when it was public, it can also surface posts and comments that have since been edited or removed from Reddit itself. That capability is useful, and it comes with responsibilities, which I get to further down.
Architecture: deliberately thin
There's no backend of mine in this project. The browser talks directly to the archive API, and a single Firestore collection is the only state I own.
- Filter panelReact stateKeywords, author, subreddit, dates, score, IDs.
- URLSearchParamsquery builderOnly set fields are sent.
- PullPush API/submission or /commentPublic archive search.
- Archive feedgrid or list cardsScore, author, date, preview image.
- Add to CMSFirestoreHand-off to my curation pipeline.
The stack is React 19 on Vite 7, lucide-react for icons, and the Firebase v12 SDK. Vite's base is set to /reddit-core/ so the build can be served from GitHub Pages.
Building the query
Everything happens in one executeSearch callback. The filter panel's state is turned into query parameters, and I only append the ones that are actually set, so an empty field never narrows the search by accident:
const type = searchFor === 'submission' ? 'submission' : 'comment'; const params = new URLSearchParams({ size: '200', sort: sortOrder, sort_type: sortBy, }); if (query) params.append('q', query); if (subreddit) params.append('subreddit', subreddit); if (username) params.append('author', username); if (ids) params.append('ids', ids); if (enableAfter) { const dt = new Date(`${afterDate}T${afterTime}:00`); params.append('after', Math.floor(dt.getTime() / 1000)); }
A few choices worth explaining:
- Date and time, not just date. The archive takes Unix epoch seconds, so I let you pick a time as well as a day. Each bound has its own "Enable" checkbox, so an open-ended range is one click away. The defaults run from 2010-01-01 to today.
- Score as an operator plus a value. The API accepts expressions like
>100, so the UI is a Greater/Less/Equal dropdown next to a number. A score of 0 means "don't filter". - Sort is exposed directly. Score, creation date, or comment count, ascending or descending. Sorting by date ascending inside a narrow window is the closest thing to a time machine for a subreddit.
- Posts vs comments just switches the endpoint between
/submission/and/comment/.
The page size started at 50. In April I raised it to 200, because for research you want to skim a wide slice and then narrow the filters. Paging through tiny result sets was slower than reading a larger one.
Being polite to a free API
PullPush is a free, community-run service, and it rate-limits. A search that fails once shouldn't fail for good, but it also shouldn't hammer the server. So the fetch retries up to three times with exponential backoff, waiting 1 s, then 2 s, then 4 s, before showing an error:
} catch (err) { if (retryCount < 3) { const delay = Math.pow(2, retryCount) * 1000; setTimeout(() => executeSearch(retryCount + 1), delay); } else { setError("Failed to fetch data. PullPush might be rate-limiting."); setIsSearching(false); } }
Rendering messy data
Archive records aren't uniform. A post might have a preview with several resolutions, only a source image, a direct image url, a thumbnail, or nothing at all. Comments have a body instead of a title. PostCard handles this in a fixed order. For previews it picks the resolution whose width is closest to 640 px, which is enough for a card without pulling the full-size original. It also decodes the & entities Reddit leaves in preview URLs. Any field that might not be a string goes through a small renderContent guard so a stray object or null can't crash the render.
The CMS hand-off
The button on every card says Add to CMS. reddit-core is one of several feeders into a small Firestore-backed content store I use when assembling curation posts. Another is a right-click browser extension that does the same thing from reddit.com itself. The app signs in anonymously and writes the post under a per-ID document:
// Path: /artifacts/quora-cms/public/data/content/{postId} const postRef = doc(db, 'artifacts', CMS_APP_ID, 'public', 'data', 'content', postId); const sanitizedPost = JSON.parse(JSON.stringify(post)); await setDoc(postRef, { ...sanitizedPost, saved_at: serverTimestamp(), source: 'PullPush Dashboard' });
Keying the document on the Reddit ID means saving the same post twice just overwrites it instead of creating a duplicate. The JSON.parse(JSON.stringify(...)) round-trip strips anything Firestore can't store. The UI tracks in-flight and completed saves in two Sets, so each card shows a spinner and then "In CMS".
What's rough, honestly
This is a working tool I use, not a polished product, and the code shows it:
| Area | State |
|---|---|
| Search target (posts/comments), author, subreddit, dates, score, IDs, sort | Wired to the API |
| "Search By: Title / Body / Both" toggle | UI only, not sent to the API yet |
| "Highlight keywords" checkbox | UI only, not implemented |
| Timezone label | Hard-coded to IST; dates use the browser's local time |
| Firestore writes | Anonymous auth; safety depends entirely on Firestore security rules |
The README is also still the Vite template. Next on the list: wire up the title/body split (the archive API, like Pushshift before it, has field-specific parameters for this), add real highlighting, add cursor-based pagination so 200 isn't a hard ceiling, and move the "Add to CMS" path behind an authenticated user instead of anonymous sign-in.
Faceted search over Reddit's public archives, with a hand-off to a curation CMS.
git clone https://github.com/shubhrgunjan/reddit-core
The lesson I keep relearning with small tools like this is that the value is in the filters, not the UI. Native Reddit search and reddit-core read the same content. What made the difference was being able to ask for "this author, this subreddit, this week of 2019, score above 500" in one query.