I wanted to build a financial dashboard that covers every public company in the US. That's 10,000+ unique pages — each with charts, tables, and real-time data from the SEC.
The catch? I wanted to host it for free on Vercel's hobby tier.
Here's how I did it, and the bugs that almost killed the project.
The result
secedgardata.com — a free dashboard for 10,000+ public companies.
Search any ticker, see revenue charts, income statements, balance sheets, and SEC filing history. No login, no API key.
The problem: 10,000 pages that can't be static
My backend (FastAPI on a home server) serves financial data for every SEC-registered company. I wanted each company to have its own page: /stock/AAPL, /stock/MSFT, /stock/PLTR, and so on.
The naive approach — generateStaticParams for all tickers — was out:
- Build time: 10,000+ API calls at build = minutes of build time
- Build server can't reach my backend: Vercel's build servers are in AWS. My backend runs on a mini PC at home behind Tailscale. Connection refused.
That second one burned me. The build looked fine locally, but every deploy on Vercel failed with ECONNRESET.
The fix: On-demand ISR with no static params
// src/app/stock/[ticker]/page.tsx
export const revalidate = 86400; // 24 hours
export const dynamicParams = true;
// No generateStaticParams — all pages are on-demand
That's it. When someone visits /stock/AAPL:
- Next.js calls my backend, renders the page
- Caches it at the edge for 24 hours
- Every subsequent visitor gets the cached version instantly
First visit is ~800ms. After that, it's <50ms from Vercel's CDN.
Mistake #1: Error handling that caches 404s
My API client originally threw errors on network failures:
// ❌ Bad: throws on network errors
const resp = await fetch(url);
if (!resp.ok) throw new Error(`API error: ${resp.status}`);
The problem: if my backend was temporarily down, Next.js would cache the error page as a 404. And that cached 404 would persist for 24 hours.
Amazon's page returned 404 for an entire day because of one transient network blip.
// ✅ Good: return null, let the page show graceful fallback
try {
const resp = await fetch(url, {
headers: { "x-dashboard-key": API_KEY },
next: { revalidate: 86400 },
});
if (!resp.ok) return null;
return resp.json();
} catch {
return null;
}
Mistake #2: Sentry not working because of Next.js 15
I set up @sentry/nextjs with the standard sentry.client.config.ts. Deployed. Threw test errors. Nothing appeared in Sentry.
Spent an hour checking DSN, environment variables, tunnel routes — everything looked correct. Then I checked the browser console: no Sentry logs at all. The file wasn't even being loaded.
Turns out Next.js 15 with Turbopack ignores sentry.client.config.ts. You need src/instrumentation-client.ts instead:
// src/instrumentation-client.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "your-dsn-here",
tracesSampleRate: 0.1,
});
This is documented in the Sentry SDK source but easy to miss. If you're on Next.js 15 and Sentry events aren't showing up — this is probably why.
The sitemap trick: 10,464 URLs
Google needs to know about all 10,000+ pages. I added a dynamic sitemap:
// src/app/sitemap.ts
export const revalidate = 86400;
export default async function sitemap() {
const tickers = await getAllTickers(); // ~10,464 tickers
return tickers.map((t) => ({
url: `https://secedgardata.com/stock/${t.ticker}`,
changeFrequency: "weekly",
priority: 0.8,
}));
}
Submitted to Google Search Console. Got 10,464 URLs recognized on day one.
One gotcha: make sure the domain in your sitemap matches your Search Console property. I had a stale fallback URL from development and Google rejected all 10,464 URLs as "not permitted." Felt great.
The stack
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 15 (App Router) | ISR, Server Components |
| Styling | Tailwind + shadcn/ui | Fast to build, dark mode free |
| Charts | Recharts | Lightweight, React native |
| Backend | FastAPI (Python) | Already had it for my API |
| Hosting | Vercel (free tier) | ISR + edge caching |
| Monitoring | Sentry + Vercel Analytics | Error tracking + PV |
Total hosting cost: $0/month (Vercel free tier handles it fine with ISR caching).
For developers: the same data as an API
The backend that powers this dashboard is also available as a REST API:
pip install sec-edgar-sdk
from sec_edgar import SecEdgar
api = SecEdgar("YOUR_RAPIDAPI_KEY")
for year in api.revenue("AAPL", limit=5):
print(f"FY{year['fiscal_year']}: ${year['value']:,.0f}")
- REST API on RapidAPI — Free tier: 100 req/month
- Python SDK on PyPI
- Source on GitHub
Try it: secedgardata.com
If you hit any bugs or have feature requests, let me know in the comments.


Top comments (0)