<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged netlify</title>
<link>https://coderlegion.com/tag/netlify</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to Deploy Your Website for Free (Vercel, Netlify, GitHub Pages)</title>
<link>https://coderlegion.com/23002/how-to-deploy-your-website-for-free-vercel-netlify-github-pages</link>
<description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Building your first project is one milestone. Actually getting it live on the internet, with a real URL you can share, is a completely different feeling — and it used to be way more complicated than it is now.&lt;/p&gt;
&lt;p&gt;Today, deploying a frontend project is genuinely a 5-minute job with the right platform. Here are the three most beginner-friendly options and exactly how to use each one.&lt;/p&gt;
&lt;h2 id=&quot;option-1-vercel-best-for-react-next-js-projects-&quot;&gt;Option 1: Vercel (Best for React/Next.js Projects)&lt;/h2&gt;
&lt;p&gt;Vercel is built by the creators of Next.js, and it's my personal go-to for React projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Push your project to a GitHub repository (see Article 7 if you need a refresher)&lt;/li&gt;
&lt;li&gt;Go to vercel.com and sign up with your GitHub account&lt;/li&gt;
&lt;li&gt;Click &quot;Add New Project&quot;&lt;/li&gt;
&lt;li&gt;Select your repository&lt;/li&gt;
&lt;li&gt;Vercel auto-detects your framework (React, Next.js, Vite, etc.) and sets the build settings  automatically&lt;/li&gt;
&lt;li&gt;Click &quot;Deploy&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's it. Within a minute or two, you get a live URL like &lt;strong&gt;your-project.vercel.app&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus&lt;/strong&gt;: Every time you push new code to GitHub, Vercel automatically redeploys your site. No manual re-upload, ever.&lt;/p&gt;
&lt;h2 id=&quot;option-2-netlify-great-all-rounder-&quot;&gt;Option 2: Netlify (Great All-Rounder)&lt;/h2&gt;
&lt;p&gt;Netlify works almost identically to Vercel and is equally beginner-friendly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Push your project to GitHub&lt;/li&gt;
&lt;li&gt;Go to netlify.com and sign up with GitHub&lt;/li&gt;
&lt;li&gt;Click &quot;Add new site&quot; → &quot;Import an existing project&quot;&lt;/li&gt;
&lt;li&gt;Select your repository&lt;/li&gt;
&lt;li&gt;Set your build command (for a Vite React app, this is usually npm run build) and publish directory (dist)&lt;/li&gt;
&lt;li&gt;Click &quot;Deploy site&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You'll get a URL like &lt;strong&gt;your-project.netlify.app&lt;/strong&gt;, with the same automatic redeploy behavior on every push.&lt;/p&gt;
&lt;p&gt;Netlify extra: It has a built-in drag-and-drop deploy option too — for a plain HTML/CSS/JS site with no build step, you can literally drag your project folder into the browser and it goes live instantly.&lt;/p&gt;
&lt;h2 id=&quot;option-3-github-pages-simplest-for-static-sites-&quot;&gt;Option 3: GitHub Pages (Simplest for Static Sites)&lt;/h2&gt;
&lt;p&gt;GitHub Pages is a solid choice for portfolio sites and static HTML/CSS/JS projects — it's what many developers use for their own portfolio (mine included).&lt;/p&gt;
&lt;p&gt;Steps for a React (Vite) project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install the gh-pages package:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;npm install gh-pages --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;In package.json, add:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&quot;homepage&quot;: &quot;https://yourusername.github.io/your-repo-name&quot;,
&quot;scripts&quot;: {
  &quot;predeploy&quot;: &quot;npm run build&quot;,
  &quot;deploy&quot;: &quot;gh-pages -d dist&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Run:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;npm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;In your GitHub repo settings, go to Pages and confirm the source is set to the &lt;strong&gt;gh-pages&lt;/strong&gt; branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your site goes live at &lt;strong&gt;yourusername.github.io/your-repo-name.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a plain HTML/CSS/JS project (no build step):&lt;/p&gt;
&lt;p&gt;Just push your files to GitHub, go to Settings → Pages, select your main branch as the source, and save. Done.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vercel — Best for React and Next.js projects. Offers a free custom domain.&lt;/li&gt;
&lt;li&gt;Netlify — Best for any frontend framework, with built-in form handling and serverless functions. Offers a free custom domain.&lt;/li&gt;
&lt;li&gt;GitHub Pages — Best for simple static websites and portfolios. Supports a free custom domain with a little setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;My honest recommendation:&lt;/strong&gt; use Vercel or Netlify for client and freelance projects — they handle builds, environment variables, and redeploys more smoothly. Use GitHub Pages for personal portfolio projects where simplicity matters more than advanced features.&lt;/p&gt;
&lt;h2 id=&quot;common-beginner-mistakes&quot;&gt;Common Beginner Mistakes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Forgetting to set the correct build/publish directory. For Vite projects, it's &lt;strong&gt;dist&lt;/strong&gt;. For Create React App, it's &lt;strong&gt;build&lt;/strong&gt;. Get this wrong and you'll see a blank page.&lt;/li&gt;
&lt;li&gt;Hardcoding API keys directly in your code instead of using environment variables — both Vercel and Netlify let you set these securely in project settings.&lt;/li&gt;
&lt;li&gt;Not testing the production build locally first. Run &lt;strong&gt;npm run build&lt;/strong&gt; and &lt;strong&gt;npm run preview&lt;/strong&gt; (Vite) before deploying, to catch build errors early.&lt;/li&gt;
&lt;li&gt;Forgetting the &lt;strong&gt;homepage&lt;/strong&gt; field for GitHub Pages — without it, your assets (CSS, images) often break after deployment.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;There's no excuse to keep a finished project sitting only on your local machine anymore. Pick Vercel or Netlify, connect your GitHub repo, and you'll have a shareable, live link in minutes — something you can put directly in a portfolio, a Fiverr gig, or a job application.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Muhammad Farhan&lt;/strong&gt; is a Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan.&lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://muhammadfarhandev.netlify.app&quot;&gt;Muhammad Farhan&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23002/how-to-deploy-your-website-for-free-vercel-netlify-github-pages</guid>
<pubDate>Fri, 14 Aug 2026 05:00:00 +0000</pubDate>
</item>
<item>
<title>Best Hosting Platforms for Next.js in 2026: Who Actually Handles ISR &amp; PPR Correctly?</title>
<link>https://coderlegion.com/22746/best-hosting-platforms-for-next-js-in-2026-who-actually-handles-isr-ppr-correctly</link>
<description>&lt;p&gt;Every &quot;best hosting for Next.js&quot; post you'll find says roughly the same thing: Vercel is great, Netlify is a solid alternative, Cloudflare is fast, AWS is for enterprises. None of them tell you what actually happens when you deploy a page using &lt;strong&gt;Incremental Static Regeneration (ISR)&lt;/strong&gt; or &lt;strong&gt;Partial Prerendering (PPR)&lt;/strong&gt; to platforms that aren't Vercel.&lt;/p&gt;
&lt;p&gt;That's the gap this post fills. Instead of comparing dashboards and pricing pages, we're going to look at what each platform's adapter &lt;em&gt;actually does&lt;/em&gt; with ISR and PPR — because &quot;supported&quot; doesn't mean the same thing everywhere. This article focuses on architecture and feature behavior rather than benchmark results, since latency varies significantly by region, workload, cache hit ratio, and deployment configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-distinction-nobody-explains-functional-vs-performance-fidelity&quot;&gt;The Distinction Nobody Explains: Functional vs. Performance Fidelity&lt;/h3&gt;
&lt;p&gt;This is the single most useful framing in this whole post, and it comes straight from Next.js's own platform documentation.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt; &lt;strong&gt;Tip:&lt;/strong&gt; Next.js draws a line between two levels of platform support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Functional fidelity&lt;/strong&gt; — the feature works correctly. Your ISR page revalidates, your PPR shell renders.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance fidelity&lt;/strong&gt; — the feature works &lt;em&gt;optimally&lt;/em&gt;. The static shell is served from the CDN edge in milliseconds, not from a server function in another region.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;A platform can have full functional fidelity for PPR (it renders correctly) while having much lower performance fidelity than Vercel (it's served from a function instead of true edge-cached HTML). Both are &quot;PPR support&quot; — but they perform very differently in production. Keep this distinction in mind for every platform below.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-isr-and-ppr-are-hard-for-non-vercel-platforms&quot;&gt;Why ISR and PPR Are Hard for Non-Vercel Platforms&lt;/h3&gt;
&lt;p&gt;Quick recap if you need it: &lt;strong&gt;ISR&lt;/strong&gt; lets a static page revalidate itself in the background after a set time, without a full rebuild. &lt;strong&gt;PPR&lt;/strong&gt; (the rendering mode behind &lt;strong&gt;Cache Components&lt;/strong&gt;, Next.js's &lt;code&gt;&quot;use cache&quot;&lt;/code&gt; model) splits a single page into a static shell plus dynamic &quot;holes&quot; that stream in separately.&lt;/p&gt;
&lt;p&gt;Both features assume something most CDNs weren't built for: that cached HTML can be &lt;strong&gt;mutated after deployment&lt;/strong&gt; — purged, regenerated, and partially streamed — rather than treated as an immutable file tied to one deploy. Traditional CDN deploy models (Netlify, Cloudflare Pages, AWS Amplify, and others all once worked this way) assume deployed assets never change until the &lt;em&gt;next&lt;/em&gt; deploy. ISR and PPR break that assumption on purpose.&lt;/p&gt;
&lt;p&gt;This is exactly why Next.js, Netlify, Cloudflare, AWS Amplify, and Google Cloud spent over a year building a shared &lt;strong&gt;Deployment Adapter API&lt;/strong&gt;, which went stable in &lt;strong&gt;Next.js 16.2 (March 2026)&lt;/strong&gt;. Before this, most non-Vercel platforms were reverse-engineering Vercel's undocumented internal build output to support ISR at all. Now there's a public, versioned contract every platform builds against — including Vercel itself.&lt;/p&gt;
&lt;p&gt;That context matters because it means platform support for these features is improving rapidly and consistently right now, not stagnant.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;platform-by-platform-breakdown&quot;&gt;Platform-by-Platform Breakdown&lt;/h3&gt;
&lt;h4 id=&quot;vercel&quot;&gt;Vercel&lt;/h4&gt;
&lt;p&gt;Vercel built Next.js, so unsurprisingly it has both full functional &lt;strong&gt;and&lt;/strong&gt; the highest performance fidelity for ISR and PPR.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ISR:&lt;/strong&gt; Native. Stale content serves instantly while regeneration happens in the background, with sub-second revalidation propagation across their edge network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PPR / Cache Components:&lt;/strong&gt; This is where the gap is biggest. PPR's design intentionally splits a response into a CDN-cacheable static shell plus a dynamically-stitched stream appended at request time. As of this writing, &lt;strong&gt;Vercel is the only platform that stitches that static shell and the dynamic stream together at the CDN edge itself&lt;/strong&gt; — every other platform currently resolves PPR through a server/function layer instead of true edge stitching.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/products/[id]/page.tsx — works identically everywhere,
// but only Vercel currently serves the static part from true edge cache
async function getProduct(id: string) {
  &quot;use cache&quot;;
  const res = await fetch(`https://api.example.com/products/${id}`);
  return res.json();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams who want zero deployment-config overhead and the fastest possible PPR/ISR performance out of the box, and don't mind paying for that convenience.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Assuming Vercel's free Hobby tier scales like its paid tier. Function execution time, image optimization, and bandwidth all meter separately — a project that feels free in development can post a real bill in production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;netlify&quot;&gt;Netlify&lt;/h4&gt;
&lt;p&gt;Netlify's Next.js Runtime is an officially maintained adapter, tested against every new stable Next.js release.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ISR:&lt;/strong&gt; Fully supported, including both time-based and on-demand revalidation, via Netlify's fine-grained caching primitives for the Full Route Cache and Data Cache.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PPR:&lt;/strong&gt; Supported — Netlify provisions a serverless Function specifically to handle SSR, ISR, &lt;strong&gt;and PPR&lt;/strong&gt;. This gives full functional fidelity, but the static shell isn't served straight from CDN edge cache the way Vercel's is — it still runs through that Function layer, which is the performance-fidelity gap described above.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// next.config.ts — no Netlify-specific config needed for PPR itself,
// the adapter detects and provisions the right Functions automatically
const nextConfig = {
  cacheComponents: true,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams who want Vercel-like functional completeness with a different pricing model or existing Netlify infrastructure (Forms, Identity, etc.).&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Pointing Next.js rewrites at static files inside &lt;code&gt;public/&lt;/code&gt; — Netlify's adapter doesn't support that pattern, so routes that worked locally can silently 404 in production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cloudflare-workers-via-opennext-&quot;&gt;Cloudflare Workers (via OpenNext)&lt;/h4&gt;
&lt;p&gt;Cloudflare doesn't have a native Next.js runtime — you deploy through the community-maintained &lt;code&gt;@opennextjs/cloudflare&lt;/code&gt; adapter, which transforms Next.js's build output into something Workers can run.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ISR:&lt;/strong&gt; Supported through the adapter's caching layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PPR:&lt;/strong&gt; Documented as supported for the App Router, but it's newer and still maturing — Cloudflare's own docs list PPR alongside features like Node Middleware as areas with ongoing compatibility work. A dedicated, verified Cloudflare adapter built on Next.js's new stable Adapter API is in active development, which should close most remaining gaps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The catch:&lt;/strong&gt; image optimization needs to be delegated to Cloudflare Images or configured separately — &lt;code&gt;next/image&lt;/code&gt; doesn't work out of the box the way it does on Vercel.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Cloudflare-specific deploy flow
npm install @opennextjs/cloudflare
npx @opennextjs/cloudflare
npx wrangler deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams already invested in Cloudflare's edge network (Workers, D1, KV, R2) who want Next.js to live in the same ecosystem, and who can tolerate being slightly behind the bleeding edge on newer caching features.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Forgetting the &lt;code&gt;nodejs_compat&lt;/code&gt; compatibility flag in &lt;code&gt;wrangler.toml&lt;/code&gt; / &lt;code&gt;wrangler.jsonc&lt;/code&gt;. Without it, plenty of Next.js server dependencies simply fail at runtime with confusing errors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;aws-amplify&quot;&gt;AWS Amplify&lt;/h4&gt;
&lt;p&gt;Amplify has supported Next.js SSR for a while, but it's noticeably behind on the newer caching model.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ISR:&lt;/strong&gt; Time-based revalidation works. &lt;strong&gt;On-demand ISR (&lt;code&gt;revalidatePath&lt;/code&gt; / &lt;code&gt;revalidateTag&lt;/code&gt; triggered manually) is explicitly not supported&lt;/strong&gt; — if your app relies on on-demand revalidation, it silently won't behave as expected on Amplify.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PPR / Cache Components:&lt;/strong&gt; Not clearly documented as supported at the time of writing. Amplify's adapter has had open compatibility issues tracking Next.js 16 support, which suggests PPR support is still catching up rather than production-proven.&lt;/li&gt;
&lt;li&gt;Build output is also capped at 220 MB for SSR apps — easy to hit with image-heavy or dependency-heavy projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams already deep in AWS infrastructure (IAM, CloudFront, existing AWS billing) who need simple SSR + scheduled ISR, and who can verify PPR behavior thoroughly before relying on it in production.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Deploying with &lt;code&gt;output: &quot;export&quot;&lt;/code&gt; and expecting ISR or SSR to work. Static export disables the Next.js server runtime entirely — no SSR, no ISR, no API routes, full stop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;railway-amp-render-container-docker-platforms-&quot;&gt;Railway &amp;amp; Render (Container/Docker Platforms)&lt;/h4&gt;
&lt;p&gt;These two get grouped together because they take the same fundamental approach: you give them a Dockerfile or a Node process, and they run it. Functionally, this means &lt;strong&gt;ISR and PPR work exactly as well as they do in self-hosting&lt;/strong&gt; — because that's effectively what's happening.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Railway:&lt;/strong&gt; Has an official Next.js + Postgres deployment guide using &lt;code&gt;output: &quot;standalone&quot;&lt;/code&gt; in a Dockerfile. SSR, SSG, and ISR all work as expected since it's a real persistent Node process. PPR isn't explicitly documented but should work for the same reason — it's just &lt;code&gt;next start&lt;/code&gt;. The bigger concern reported in 2026 production usage isn't feature support, some users have reported reliability concerns around persistent storage and deployments, so production testing is important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Render:&lt;/strong&gt; Supports SSR, ISR, and API routes through its Web Service deployment type, also via Docker or Git-based builds. Same story as Railway — because it's a real Node process, feature support isn't the limiting factor. Zero-downtime rolling deploys need deliberate configuration; they're not automatic the way they are on Vercel.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams who want container-based pricing predictability without writing their own Dockerfile-to-production pipeline from scratch, and who are comfortable being early on platform maturity.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Treating Railway or Render as &quot;set and forget&quot; the way Vercel is. Both still require you to think about zero-downtime deploys, shared cache for multiple instances, and monitoring — that operational layer doesn't disappear just because there's no explicit Next.js adapter to configure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;self-hosting-docker-vps-fly-io-cloud-run-&quot;&gt;Self-Hosting (Docker, VPS, Fly.io, Cloud Run)&lt;/h4&gt;
&lt;p&gt;This is the one with the cleanest answer of all of them, straight from Next.js's own documentation:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;A single &lt;code&gt;next start&lt;/code&gt; process handles every Next.js feature correctly: Server Components, ISR, PPR, Cache Components, Server Actions, Proxy, and &lt;code&gt;after()&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Full functional fidelity, no adapter, no caveats — because there's no translation layer between your app and the framework. The tradeoffs aren't about features; they're about who's doing the operational work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;# Minimal self-hosting Dockerfile
FROM node:22-alpine AS base
WORKDIR /app
COPY . .
RUN npm ci &amp;amp;&amp;amp; npm run build

FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=base /app/.next/standalone ./
COPY --from=base /app/.next/static ./.next/static
EXPOSE 3000
CMD [&quot;node&quot;, &quot;server.js&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What you give up is everything Vercel/Netlify automate for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-instance cache coordination&lt;/strong&gt; — if you scale beyond one server, &lt;code&gt;revalidateTag()&lt;/code&gt; only invalidates the instance that handled the request by default. You need a shared cache handler (commonly Redis) so other instances learn about invalidations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streaming infrastructure&lt;/strong&gt; — PPR's time-to-first-byte advantage depends on your reverse proxy actually supporting streaming responses. Without it, the static shell and dynamic content get buffered and sent together, which still works but quietly loses PPR's main benefit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All the DevOps&lt;/strong&gt;, full stop: TLS, zero-downtime deploys, observability, scaling rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams with existing DevOps capacity, strict data residency or compliance requirements, or sustained traffic where Vercel's usage-based pricing starts to outpace flat compute costs.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Self-hosting without &lt;code&gt;output: &quot;standalone&quot;&lt;/code&gt; in &lt;code&gt;next.config.ts&lt;/code&gt;. Without it, your Docker image drags in the entire &lt;code&gt;node_modules&lt;/code&gt; tree instead of a trimmed production bundle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-comparison&quot;&gt;Quick Comparison&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Platform &lt;/th&gt;&lt;th&gt;ISR (time-based) &lt;/th&gt;&lt;th&gt;ISR (on-demand) &lt;/th&gt;&lt;th&gt;PPR / Cache Components &lt;/th&gt;&lt;th&gt;Performance Fidelity&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Vercel&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;✅ Native &lt;/td&gt;&lt;td&gt;✅ Native &lt;/td&gt;&lt;td&gt;✅ True edge-stitched shell &lt;/td&gt;&lt;td&gt;Highest&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Netlify&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;✅ Via adapter &lt;/td&gt;&lt;td&gt;✅ Via adapter &lt;/td&gt;&lt;td&gt;✅ Via Function layer &lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;✅ Via OpenNext &lt;/td&gt;&lt;td&gt; Maturing &lt;/td&gt;&lt;td&gt; Documented, still maturing &lt;/td&gt;&lt;td&gt;Medium&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AWS Amplify&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;✅ Time-based only &lt;/td&gt;&lt;td&gt;❌ Not supported &lt;/td&gt;&lt;td&gt; Unclear / catching up &lt;/td&gt;&lt;td&gt;Medium-Low&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Railway / Render&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;✅ (it's just Node) &lt;/td&gt;&lt;td&gt;✅ (it's just Node) &lt;/td&gt;&lt;td&gt;✅ (it's just Node) &lt;/td&gt;&lt;td&gt;Depends on your setup&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Self-hosted&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;✅ Full fidelity &lt;/td&gt;&lt;td&gt;✅ Full fidelity &lt;/td&gt;&lt;td&gt;✅ Full fidelity &lt;/td&gt;&lt;td&gt;You configure it&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;so-which-one-should-you-actually-use-&quot;&gt;So, Which One Should You Actually Use?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Default choice for most SaaS/MVP work:&lt;/strong&gt; Vercel. The performance-fidelity gap on PPR is real, and for most projects, not having to think about adapters or streaming infrastructure is worth the premium.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Want Vercel-level functional support on a different platform or pricing model:&lt;/strong&gt; Netlify is the most production-proven non-Vercel option for ISR and PPR today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Already living in Cloudflare's ecosystem:&lt;/strong&gt; Workers via OpenNext is viable, just confirm PPR behavior for your specific routes before launch — it's improving fast but newer than Netlify's support.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On AWS for compliance or org reasons:&lt;/strong&gt; Amplify works for SSR + scheduled ISR; don't build a feature around on-demand revalidation or PPR there yet without testing thoroughly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Want container simplicity without managing your own VPS:&lt;/strong&gt; Railway or Render — but budget time for reliability testing, especially anything stateful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Have DevOps capacity, compliance needs, or are past the point where usage-based pricing makes sense:&lt;/strong&gt; self-host. Every feature works correctly by default; you're trading platform fees for engineering time.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;does-supports-isr-mean-it-works-exactly-like-vercel-&quot;&gt;Does &quot;supports ISR&quot; mean it works exactly like Vercel?&lt;/h4&gt;
&lt;p&gt;Not necessarily. Use the functional vs. performance fidelity distinction from earlier in this post — most platforms now have functional support, but the experience (latency, edge caching, revalidation propagation speed) still varies.&lt;/p&gt;
&lt;h4 id=&quot;is-ppr-available-everywhere-yet-&quot;&gt;Is PPR available everywhere yet?&lt;/h4&gt;
&lt;p&gt;Functionally, on most major platforms — yes. With the same performance characteristics as Vercel — no, not yet. Vercel currently has unique edge-stitching infrastructure that other platforms haven't replicated.&lt;/p&gt;
&lt;h4 id=&quot;will-the-new-adapter-api-make-all-platforms-equal-eventually-&quot;&gt;Will the new Adapter API make all platforms equal eventually?&lt;/h4&gt;
&lt;p&gt;It makes them &lt;em&gt;correct&lt;/em&gt; against the same shared contract, which is a big deal for portability. It doesn't automatically make them equally &lt;em&gt;fast&lt;/em&gt; — that still depends on each platform's own CDN and edge infrastructure investment.&lt;/p&gt;
&lt;h4 id=&quot;is-self-hosting-actually-cheaper-&quot;&gt;Is self-hosting actually cheaper?&lt;/h4&gt;
&lt;p&gt;Compute usually is. Total cost isn't always — factor in the engineering hours for setup and ongoing maintenance, not just server bills, before assuming self-hosting wins on price.&lt;/p&gt;
&lt;h4 id=&quot;can-i-switch-platforms-later-if-i-start-with-vercel-&quot;&gt;Can I switch platforms later if I start with Vercel?&lt;/h4&gt;
&lt;p&gt;Yes, especially now that the Adapter API is stable and shared across platforms. It's not zero-effort, but it's far less risky than it was before this API existed.&lt;/p&gt;
&lt;h5 id=&quot;platform-migration-difficulty&quot;&gt;Platform Migration Difficulty&lt;/h5&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Move From            &lt;/th&gt;&lt;th&gt;Difficulty&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Vercel → Netlify     &lt;/td&gt;&lt;td&gt;Low&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Vercel → OpenNext    &lt;/td&gt;&lt;td&gt;Medium&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Vercel → Self-hosted &lt;/td&gt;&lt;td&gt;Medium&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Amplify → Vercel     &lt;/td&gt;&lt;td&gt;Low&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;&quot;Best Next.js hosting&quot; isn't really a single ranking — it's a tradeoff between performance fidelity, pricing model, and how much operational work you want to own. Vercel still leads on PPR/ISR performance specifically because it built the rendering model the rest of the ecosystem is now catching up to. Everyone else is closing that gap at different speeds, and the new shared Adapter API is exactly why that gap is closing faster in 2026 than it ever has before.&lt;/p&gt;
&lt;p&gt;If you're deploying something today: pick based on where your team already lives, then verify ISR/PPR behavior yourself in a staging deploy rather than trusting a docs page's checkmark — the distinction between &quot;works&quot; and &quot;works well&quot; is exactly what most comparison posts skip.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;useful-resources&quot;&gt;Useful Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/blog/nextjs-across-platforms&quot;&gt;Next.js: Across Platforms — Adapters, OpenNext, and Commitments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/guides/deploying-to-platforms&quot;&gt;Next.js Deploying to Platforms Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/guides/ppr-platform-guide&quot;&gt;Next.js PPR Platform Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/guides/self-hosting&quot;&gt;Next.js Self-Hosting Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.netlify.com/build/frameworks/framework-setup-guides/nextjs/overview/&quot;&gt;Netlify Next.js Runtime Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/&quot;&gt;Cloudflare Next.js (OpenNext) Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;continue-learning&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-rendering-strategies-explained&quot;&gt;Next.js Rendering Strategies Explained: SSR, SSG, CSR, ISR &amp;amp; Cache Components (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/saas-starter-architecture-nextjs&quot;&gt;SaaS Starter Architecture with Next.js 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22746/best-hosting-platforms-for-next-js-in-2026-who-actually-handles-isr-ppr-correctly</guid>
<pubDate>Wed, 15 Jul 2026 10:05:54 +0000</pubDate>
</item>
<item>
<title>Back-End Web Development — VPS vs Vercel vs Netlify</title>
<link>https://coderlegion.com/20868/back-end-web-development-vps-vs-vercel-vs-netlify</link>
<description>&lt;h2 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Every great web application with millions of users has an even greater back end — and it has to be. Accessing your app over a prolonged period of time will test the integrity, availability, and speed of your back end. Backend technology can be inexpensive, or it can cost you thousands of dollars. So what's the deal here?   &lt;/p&gt;
&lt;p&gt;Let me warn you that this post is highly opinionated. I am sharing the tools that I use and how I use them.&lt;/p&gt;
&lt;p&gt;For my back end that powers my web apps, I use Directus for my headless CMS, supported by a PostgreSQL and Redis database, hosted on a VPS, and managed by Coolify. Wow, that is a mouthful — and on my channel I explore how they all work together.&lt;/p&gt;
&lt;p&gt;So let's tackle each technology one at a time, starting with the VPS.&lt;/p&gt;
&lt;h2 id=&quot;what-is-a-vps-&quot;&gt;What is a VPS?&lt;/h2&gt;
&lt;p&gt;A Virtual Private Server, known as a VPS, is a virtual environment created on a physical server. Think of it like an apartment building — everyone shares the same physical structure, but each unit has its own private space, utilities, and security. While multiple VPS instances share a physical server, be careful not to confuse this with shared hosting.&lt;/p&gt;
&lt;p&gt; Each VPS is allocated its own dedicated resources, which are restricted to that VPS for as long as it is active. Therefore, the performance of a VPS is not directly affected by the usage of other VPS instances, but rather by the underlying performance of the physical server itself.&lt;/p&gt;
&lt;h2 id=&quot;what-are-the-benefits-of-a-vps-&quot;&gt;What are the benefits of a VPS?&lt;/h2&gt;
&lt;p&gt;There are several benefits to consider when deciding on virtual private server hosting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Greater control&lt;/strong&gt; — Compared to shared hosting, you have root access and can fully customize your server environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dedicated resources&lt;/strong&gt; — Allocated CPU, memory, and storage help ensure consistent performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt; — You can easily scale resources up or down to accommodate changing traffic and application demands.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost-effectiveness&lt;/strong&gt; — A VPS typically offers a balance between the affordability of shared hosting and the power of a dedicated server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved security&lt;/strong&gt; — Isolation from other users on the same physical server enhances security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Choice of operating system&lt;/strong&gt; — You can choose the operating system that best suits your needs, such as Linux or Windows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-does-it-stack-up-against-vercel-or-netlify-&quot;&gt;How does it stack up against Vercel or Netlify?&lt;/h2&gt;
&lt;p&gt;Vercel offers minimal setup with Git integration and fast deployment right out of the box. It has a free Hobby plan, but it is strictly for personal, non-commercial use. Once your app grows and needs to scale, you will need to move to a paid plan. &lt;/p&gt;
&lt;p&gt;The Pro plan starts at $20 per user per month, and also covers serverless functions. Despite the name, serverless code still runs on a physical server — however, the infrastructure management, such as scaling, security patching, and provisioning, is handled entirely by the cloud provider. The main factor to watch carefully is cost. &lt;/p&gt;
&lt;p&gt;Vercel uses usage-based billing, and costs can escalate quickly — especially since Turbo build machines became the default for new Pro projects in February 2026, at 0.126USD  per build minute. A moderate team workflow can generate over $400 per month in build costs alone, before bandwidth and compute charges. I have seen some alarming Vercel bills shared online by customers, which means you need to fully understand the pricing model before you commit.&lt;/p&gt;
&lt;p&gt;Netlify has historically been more suited to static sites and composable web applications. Composable applications are software systems built from independent, interchangeable modules rather than a single rigid code base. Netlify also has Git integration, customizable build plugins, and serverless functions. Netlify moved to a credit-based pricing model in September 2025, which is &lt;br&gt;
designed to simplify metered billing. &lt;/p&gt;
&lt;p&gt;As of April 2026, the Pro plan costs 20USD per month and now includes unlimited team member seats, which is an improvement over the previous per-seat model. However, teams working on active projects with real traffic can burn through their credit allocation quickly, and credit pack add-ons at 10USD per 1,500 credits mean teams can regularly spend 40USD to 80USD or more per month beyond the base subscription. Your overall control is also more limited compared to a VPS, and you will still face a monthly cost as your app grows.&lt;/p&gt;
&lt;h2 id=&quot;now-the-vps-&quot;&gt;Now the VPS.&lt;/h2&gt;
&lt;p&gt;VPS prices can grow as your app grows, but in my experience at a slower and more predictable rate — with no surprise charges. However, if you manage a VPS yourself, it requires understanding web servers, firewalls, operating systems, caching, and more. It can get complex very quickly and may cause you to spend more time managing the server than building and scaling your app.&lt;/p&gt;
&lt;p&gt;That is why some developers and business owners hire a team to handle it. Small teams may not have the budget for a dedicated infrastructure team, so they often opt for a more convenient managed solution.&lt;/p&gt;
&lt;p&gt;There is one tool, however, that I think is worth mentioning and is a genuine game changer — and that is Coolify. In a later post I will go into more detail about what Coolify is and what it does. Coolify is a self-hostable platform that makes managing your VPS significantly easier. It offers Git integration, runs apps in Dockerized containers, handles HTTPS automatically, and includes a built-in web server that works out of the box with no manual configuration required. This makes the VPS a very real and competitive option worth considering.&lt;/p&gt;
&lt;p&gt;If you choose a solid hosting provider and pair it with the right tools, you can make this an absolute dream to work with.&lt;/p&gt;
&lt;p&gt;Now don’t get me wrong, even with Coolify there are some configurations that you will have to make, but I do believe a VPS is a great solution for a start up, mid-size or enterprise scaled app, with the right tools.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;• https://cloud.google.com/learn/what-is-a-virtual-private-server 
• https://aws.amazon.com/what-is/vps/ 
• https://www.ibm.com/think/topics/vps 
• https://www.scalahosting.com/blog/what-is-a-vps-technical-explanation/ 
• https://www.dreamhost.com/blog/beginners-guide-vps/ 
• https://vercel.com/pricing 
• https://www.netlify.com/pricing/ 
• https://bejamas.com/blog/self-hosting-vs-vercel-and-netlify-which-solution-is-right
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20868/back-end-web-development-vps-vs-vercel-vs-netlify</guid>
<pubDate>Thu, 18 Jun 2026 03:34:03 +0000</pubDate>
</item>
<item>
<title>Why Your API Calls Are Being Blocked in the Browser (and How to Fix It in 12 Lines)</title>
<link>https://coderlegion.com/15764/why-your-api-calls-are-being-blocked-in-the-browser-and-how-to-fix-it-in-12-lines</link>
<description>&lt;p&gt;&lt;em&gt;The CORS error that kills every developer’s first API integration — and the serverless proxy pattern that solves it permanently.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you’ve ever built something that calls an external API directly from the browser and seen this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError: Failed to fetch
Access to fetch at 'https://api.example.com' from origin 'https://yourapp.com' 
has been blocked by CORS policy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This post is for you.&lt;/p&gt;
&lt;p&gt;I hit this exact wall building a live subscription dashboard on RevenueCat’s Charts API. The API works perfectly. The dashboard works perfectly. But call it from a browser and you get a silent network error with no useful explanation.&lt;/p&gt;
&lt;p&gt;Here’s exactly what’s happening, why it’s actually correct behavior, and how to fix it in 12 lines of JavaScript.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-cors-actually-is&quot;&gt;What CORS Actually Is&lt;/h3&gt;
&lt;p&gt;CORS stands for Cross-Origin Resource Sharing. It’s a browser security mechanism that prevents a web page from making requests to a different domain than the one that served the page.&lt;/p&gt;
&lt;p&gt;When your dashboard at &lt;code&gt;yourapp.netlify.app&lt;/code&gt; tries to call &lt;code&gt;api.revenuecat.com&lt;/code&gt;, the browser first sends a preflight request asking the API: “Hey, do you allow requests from this origin?”&lt;/p&gt;
&lt;p&gt;If the API doesn’t respond with the right headers — specifically &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; — the browser blocks the request entirely. Not the server. The browser.&lt;/p&gt;
&lt;p&gt;This is important: &lt;strong&gt;the API call never even reaches the server in most cases.&lt;/strong&gt; The browser kills it first.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-apis-block-browser-requests-on-purpose&quot;&gt;Why APIs Block Browser Requests on Purpose&lt;/h3&gt;
&lt;p&gt;Here’s the part nobody explains: some APIs block browser requests intentionally. RevenueCat’s Charts API is one of them.&lt;/p&gt;
&lt;p&gt;The reason is simple. The Charts API requires a secret v2 key with elevated permissions. Secret keys should never live in client-side code — they’re visible to anyone who opens DevTools and looks at the network tab.&lt;/p&gt;
&lt;p&gt;By not adding CORS headers, RevenueCat is enforcing good security hygiene. They’re saying: this key belongs on a server, not in a browser.&lt;/p&gt;
&lt;p&gt;So the CORS error isn’t a bug. It’s the API telling you: route this through a server.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix-a-serverless-proxy&quot;&gt;The Fix: A Serverless Proxy&lt;/h3&gt;
&lt;p&gt;The solution is a proxy — a small server-side function that sits between your browser and the API. Your browser calls the proxy (same origin, no CORS issue). The proxy holds your secret key in an environment variable and forwards the request to the API with proper authentication.&lt;/p&gt;
&lt;p&gt;Here’s the full pattern using Netlify Functions:&lt;/p&gt;
&lt;h4 id=&quot;step-1-create-the-function-file&quot;&gt;Step 1 — Create the function file&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;your-project/
  netlify/
    functions/
      rc-proxy.js    ← create this
  index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-2-write-the-proxy-12-lines-&quot;&gt;Step 2 — Write the proxy (12 lines)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// netlify/functions/rc-proxy.js
exports.handler = async (event) =&amp;gt; {
  const { path, params } = JSON.parse(event.body);
  const url = `https://api.revenuecat.com/v2${path}?${new URLSearchParams(params)}`;

  const res = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${process.env.RC_API_KEY}`,
      'Content-Type': 'application/json'
    }
  });

  return {
    statusCode: res.status,
    body: JSON.stringify(await res.json())
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-3-set-your-environment-variable&quot;&gt;Step 3 — Set your environment variable&lt;/h4&gt;
&lt;p&gt;In your Netlify dashboard: &lt;strong&gt;Site Settings → Environment Variables → Add variable&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Key:   RC_API_KEY
Value: your_secret_api_key_here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your key never touches the browser. It lives in Netlify’s encrypted environment variable store.&lt;/p&gt;
&lt;h4 id=&quot;step-4-call-the-proxy-from-your-frontend&quot;&gt;Step 4 — Call the proxy from your frontend&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// In your browser-side JavaScript
async function fetchChart(projectId, chartName, params) {
  const res = await fetch('/.netlify/functions/rc-proxy', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      path: `/projects/${projectId}/charts/${chartName}`,
      params: {
        resolution: params.resolution || 'week',
        start_time: params.startDate,
        end_time: params.endDate
      }
    })
  });

  const data = await res.json();
  return data.values || [];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same origin. No CORS. Key never exposed. Done.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h3&gt;
&lt;p&gt;The browser’s CORS restriction only applies to cross-origin requests. When your frontend at &lt;code&gt;yourapp.netlify.app&lt;/code&gt; calls &lt;code&gt;yourapp.netlify.app/.netlify/functions/rc-proxy&lt;/code&gt; — that’s the same origin. No preflight, no CORS check, no block.&lt;/p&gt;
&lt;p&gt;The proxy then makes the cross-origin request to RevenueCat server-side. Servers don’t have CORS restrictions. Only browsers do.&lt;/p&gt;
&lt;p&gt;The request chain looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Browser → /.netlify/functions/rc-proxy → api.revenuecat.com
         [same origin, no CORS]         [server-to-server, no CORS]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;testing-without-deploying&quot;&gt;Testing Without Deploying&lt;/h3&gt;
&lt;p&gt;You don’t need to deploy to test this. Use Netlify Dev locally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g netlify-cli
netlify dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Netlify Dev spins up a local server that emulates the Functions environment, reads your &lt;code&gt;.env&lt;/code&gt; file, and serves your frontend — all at &lt;code&gt;localhost:8888&lt;/code&gt;. Your proxy runs at &lt;code&gt;localhost:8888/.netlify/functions/rc-proxy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;.env&lt;/code&gt; file for local development:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RC_API_KEY=your_secret_key_here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Never commit &lt;code&gt;.env&lt;/code&gt; to git. Add it to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;extending-the-pattern&quot;&gt;Extending the Pattern&lt;/h3&gt;
&lt;p&gt;The same 12-line proxy works for any API that blocks browser requests. Change the base URL and you’ve got a proxy for:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stripe:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const url = `https://api.stripe.com/v1${path}`;
// headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}` }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;OpenAI:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const url = `https://api.openai.com/v1${path}`;
// headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Any REST API with secret key auth:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const url = `https://api.yourservice.com${path}`;
// headers: { 'Authorization': `Bearer ${process.env.YOUR_SECRET_KEY}` }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern is identical. One function, any API.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-about-rate-limits-&quot;&gt;What About Rate Limits?&lt;/h3&gt;
&lt;p&gt;The proxy is also a good place to handle rate limiting. If the API returns a &lt;code&gt;429&lt;/code&gt;, you can catch it and return a meaningful error to the frontend instead of a generic network failure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;exports.handler = async (event) =&amp;gt; {
  const { path, params } = JSON.parse(event.body);
  const url = `https://api.revenuecat.com/v2${path}?${new URLSearchParams(params)}`;

  const res = await fetch(url, {
    headers: { 'Authorization': `Bearer ${process.env.RC_API_KEY}` }
  });

  // Surface rate limit info to the frontend
  if (res.status === 429) {
    const retryAfter = res.headers.get('Retry-After') || '60';
    return {
      statusCode: 429,
      body: JSON.stringify({ 
        error: 'Rate limited', 
        retryAfter: parseInt(retryAfter) 
      })
    };
  }

  return {
    statusCode: res.status,
    body: JSON.stringify(await res.json())
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now your frontend can tell the user exactly when to retry instead of showing a confusing error.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-full-working-dashboard&quot;&gt;The Full Working Dashboard&lt;/h3&gt;
&lt;p&gt;If you want to see this proxy pattern in action with a complete frontend, the live RevenueCat Charts API dashboard I built is here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://rc-charts-dashboard.netlify.app&quot;&gt;→ rc-charts-dashboard.netlify.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The full blog post explaining what the Charts API returns, how the auth works, and how to build the dashboard from scratch is here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jordan_sterchele/i-built-a-live-subscription-dashboard-on-revenuecats-charts-api-in-one-html-file-5alc&quot;&gt;→ I Built a Live Subscription Dashboard on RevenueCat’s Charts API in One HTML File&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re building on any API that blocks CORS and hitting a wall — drop a comment. I’ll help you wire up the proxy.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Disclosure: This post was produced by AXIOM, an agentic developer advocacy workflow powered by Anthropic’s Claude, operated by Jordan Sterchele. Human-reviewed before publication.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15764/why-your-api-calls-are-being-blocked-in-the-browser-and-how-to-fix-it-in-12-lines</guid>
<pubDate>Fri, 24 Apr 2026 22:59:31 +0000</pubDate>
</item>
<item>
<title>Weather Service Project (Part 1): Building the Data Collector (Python, GitHub Actions or Netlify)</title>
<link>https://coderlegion.com/10040/weather-service-project-part-building-the-data-collector-python-github-actions-netlify</link>
<description>&lt;p&gt;As I mentioned in a previous post, one of my goals with Datalaria is to get my hands dirty with projects that allow me to learn and connect different technologies in the data world. Today, we begin a series dedicated to one of those projects: the creation of a &lt;strong&gt;complete global weather service&lt;/strong&gt;, from data collection to visualization and prediction, all serverless and using free tools.&lt;/p&gt;
&lt;p&gt;In this first installment, we will focus on the &lt;strong&gt;heart of the system: the backend data collector&lt;/strong&gt;. We'll see how to build a &quot;robot&quot; that works for us 24/7, connecting to an external API, saving structured information, and doing all this automatically and for free. Let's dive in!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app_openweather_part1_backend/AI_App_Weather_Image.png&quot; alt=&quot;Conceptual image of Weather Service&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-first-step-talking-to-the-openweathermap-api&quot;&gt;The First Step: Talking to the OpenWeatherMap API&lt;/h4&gt;
&lt;p&gt;Every weather service needs a data source. I chose &lt;a rel=&quot;nofollow&quot; href=&quot;https://openweathermap.org/&quot;&gt;OpenWeatherMap&lt;/a&gt; for its popularity and generous free plan. The initial process is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Register&lt;/strong&gt;: Create an account on their website.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get the API Key&lt;/strong&gt;: Generate a unique key that will identify us in each call. It's like our &quot;key&quot; to access their data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store the Key&lt;/strong&gt;: &lt;strong&gt;Never&lt;/strong&gt; directly in the code! We'll discuss this further below.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With the key in hand (or almost!), I wrote a first &lt;code&gt;test_clima.py&lt;/code&gt; script to test the connection using Python's fantastic &lt;code&gt;requests&lt;/code&gt; library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests

API_KEY = &quot;YOUR_API_KEY_HERE&quot; # Temporarily! We'll use Secrets later
CITY = &quot;Madrid&quot;
URL = f&quot;[https://api.openweathermap.org/data/2.5/weather?q=](https://api.openweathermap.org/data/2.5/weather?q=){CITY}&amp;amp;appid={API_KEY}&amp;amp;units=metric&amp;amp;lang=es&quot;

try:
    response = requests.get(URL)
    response.raise_for_status() # Raises an exception for HTTP errors (4xx or 5xx)
    data = response.json()
    print(f&quot;Temperature in {CITY}: {data['main']['temp']}°C&quot;)
except requests.exceptions.RequestException as e:
    print(f&quot;Error connecting to the API: {e}&quot;)
except KeyError as e:
    print(f&quot;Unexpected API response, key missing: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;First Obstacle Overcome (with Patience):&lt;/strong&gt; When I first ran it, I got a 401 Unauthorized error!  It turns out that OpenWeatherMap API Keys can take a few hours to activate after being generated. The lesson: sometimes, the solution is simply to wait. ⏳&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-database-why-csv-and-not-sql-&quot;&gt;The &quot;Database&quot;: Why CSV and Not SQL?&lt;/h4&gt;
&lt;p&gt;With data flowing, I needed to store it. I could have set up an SQL database (PostgreSQL, MySQL...), but that would involve complexity, a server (cost), and for this project, it was overkill.&lt;/p&gt;
&lt;p&gt;I opted for radical simplicity: &lt;strong&gt;CSV (Comma Separated Values) files&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: Easy to read and write with Python, perfectly versionable with Git (we can track changes), and sufficient for the initial data volume we'd be handling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key Logic&lt;/strong&gt;: I needed to append a new row to each city's file daily, but only write the header (&lt;code&gt;date_time&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;temperature_c&lt;/code&gt;, etc.) the first time. Python's native &lt;code&gt;csv&lt;/code&gt; library and &lt;code&gt;os.path.exists&lt;/code&gt; make this trivial:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import csv
import os
from datetime import datetime

# ... (code to fetch API data for a city) ...

now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
data_row = [now, city, temperature, ...] # List with the data
header = ['date_time', 'city', 'temperature_c', ...] # List with column names
file_name = f&quot;data/{city}.csv&quot; # We'll create a 'data' folder

# Ensure the 'data' folder exists
os.makedirs(os.path.dirname(file_name), exist_ok=True)

is_new_file = not os.path.exists(file_name)

try:
    with open(file_name, mode='a', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        if is_new_file:
            writer.writerow(header) # Write header ONLY if new file
        writer.writerow(data_row) # Append the new data row
    print(f&quot;Data saved for {city}&quot;)
except IOError as e:
    print(f&quot;Error writing to {file_name}: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-automation-robot-github-actions-to-the-rescue&quot;&gt;The Automation Robot: GitHub Actions to the Rescue&lt;/h4&gt;
&lt;p&gt;Here comes the magic: how to make this script run daily without having a server constantly on? The answer is &lt;strong&gt;GitHub Actions&lt;/strong&gt;, the automation engine integrated into GitHub. It's like having a small robot working for us for free.&lt;/p&gt;
&lt;h5 id=&quot;security-first-never-upload-your-api-key-&quot;&gt;Security First: Never Upload Your API Key!&lt;/h5&gt;
&lt;p&gt;The biggest mistake would be to upload &lt;code&gt;registrar_clima.py&lt;/code&gt; with the &lt;code&gt;API_KEY&lt;/code&gt; written directly in the code. Anyone could see it on GitHub.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Use GitHub's &lt;strong&gt;Repository Secrets&lt;/strong&gt;.&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/code&gt; in your GitHub repository.&lt;/li&gt;
&lt;li&gt;Create a new secret named &lt;code&gt;OPENWEATHER_API_KEY&lt;/code&gt; and paste your key there.&lt;/li&gt;
&lt;li&gt;In your Python script, read the key securely using &lt;code&gt;os.environ.get(&quot;OPENWEATHER_API_KEY&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;the-robot-s-brain-the-github-workflows-update-weather-yml-file&quot;&gt;The Robot's Brain: The &lt;code&gt;.github/workflows/update-weather.yml&lt;/code&gt; File&lt;/h5&gt;
&lt;p&gt;This YAML file tells GitHub Actions what to do and when:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Daily Weather Data Update

on:
  workflow_dispatch: # Allows manual triggering from GitHub
  push:
    branches: [ main ] # Triggers if changes are pushed to the main branch
  schedule:
    - cron: '0 6 * * *' # The key: triggers daily at 06:00 UTC

jobs:
  update_data:
    runs-on: ubuntu-latest # Use a free Linux virtual machine
    steps:
      - name: Checkout repository code
        uses: actions/checkout@v4 # Downloads our code

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10' # Or your preferred version

      - name: Install necessary dependencies
        run: pip install -r requirements.txt # Reads requirements.txt and installs requests, etc.

      - name: Execute data collection script
        run: python registrar_clima.py # Our main script!
        env:
          OPENWEATHER_API_KEY: ${{ secrets.OPENWEATHER_API_KEY }} # Securely injects the secret

      - name: Save new data to repository (Commit &amp;amp; Push)
        run: |
          git config user.name 'github-actions[bot]' # Identifies the 'bot'
          git config user.email 'github-actions[bot]@users.noreply.github.com'
          git add data/*.csv # Adds ONLY the modified CSV files in the 'data' folder
          # Check if there are changes before committing to avoid empty commits
          git diff --staged --quiet || git commit -m &quot;Automated weather data update &quot;
          git push # Pushes changes to the repository
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Automatic token to allow the push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;This last step is crucial!&lt;/strong&gt; The Action itself acts as a user, performing &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt;, and &lt;code&gt;git push&lt;/code&gt; of the CSV files that the Python script has just modified. This way, the updated data is saved in our repository every day.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-serverless-alternative-deployment-and-automation-with-netlify&quot;&gt;The Serverless Alternative: Deployment and Automation with Netlify&lt;/h4&gt;
&lt;p&gt;While GitHub Actions is a fantastic automation tool, for this project I decided to explore an alternative even more integrated with the &quot;serverless&quot; concept: &lt;strong&gt;Netlify&lt;/strong&gt;. Netlify not only allows us to deploy our static frontend (like GitHub Pages) but also offers serverless functions and, crucially for our backend, &lt;strong&gt;scheduled functions (or Cron Jobs)&lt;/strong&gt;.&lt;/p&gt;
&lt;h5 id=&quot;deploying-the-static-frontend-with-netlify&quot;&gt;Deploying the Static Frontend with Netlify&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Connect Your Repository&lt;/strong&gt;: The process is incredibly simple. Log in to Netlify, click &quot;Add new site,&quot; and select &quot;Import an existing project.&quot; Connect with your GitHub account and choose your Weather Service project repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Configuration&lt;/strong&gt;: Netlify will automatically detect your project. Ensure that the &quot;Build command&quot; is empty (as it's a static site with no build process) and that the &quot;Publish directory&quot; is the root of your repository (&lt;code&gt;./&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Deployment&lt;/strong&gt;: Netlify will automatically configure continuous deployment. Every time you &lt;code&gt;git push&lt;/code&gt; to your &lt;code&gt;main&lt;/code&gt; branch (or whichever branch you've configured), Netlify will rebuild and deploy your site.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;automating-the-backend-with-netlify-functions-and-cron-jobs-&quot;&gt;Automating the Backend with Netlify Functions (and Cron Jobs)&lt;/h5&gt;
&lt;p&gt;This is where Netlify Serverless Functions shine for our data collector. Instead of a GitHub Actions workflow, we can use a Netlify function to run our Python script on a schedule:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Project Structure&lt;/strong&gt;: Create a &lt;code&gt;netlify/functions/&lt;/code&gt; folder at the root of your project. Inside, you can have a Python file like &lt;code&gt;collect_weather.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Management&lt;/strong&gt;: You'll need a &lt;code&gt;requirements.txt&lt;/code&gt; file at the root of your project for Netlify to install Python dependencies (&lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;scikit-learn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;netlify.toml&lt;/code&gt; Configuration&lt;/strong&gt;: This file at your project's root is crucial for defining your functions and their schedules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[build]
  publish = &quot;.&quot; # Directory where your index.html is located
  command = &quot;&quot; # No build command needed for a static site

[functions]
  directory = &quot;netlify/functions&quot; # Where your functions are located
  node_bundler = &quot;esbuild&quot; # For JS/TS functions. Netlify will detect Python.

[[edge_functions]] # For scheduling a function (requires Netlify Edge Functions)
  function = &quot;collect_weather&quot; # The name of your function (without the .py extension)
  path = &quot;/.netlify/functions/collect_weather&quot; # The function path (can be different)
  schedule = &quot;@daily&quot; # Or use a cron string like &quot;0 6 * * *&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Python Function (&lt;code&gt;netlify/functions/collect_weather.py&lt;/code&gt;)&lt;/strong&gt;: This function will encapsulate the logic of your &lt;code&gt;registrar_clima.py&lt;/code&gt;. Netlify will execute it in a Python environment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# netlify/functions/collect_weather.py
import json
import requests
import os
import time
from datetime import datetime
import csv

# ... (all your registrar_clima.py script code goes here) ...

def handler(event, context):
    # The main call to your data collection logic would go here
    # This is a simplified example
    try:
        # Your logic to fetch and save data, generate CSVs/JSONs
            
        # For this project, the GitHub Actions approach for the backend
        # that directly commits to the repo is still simpler
        # for CSV storage. Netlify would be ideal for the frontend
        # and functions for real-time APIs or lightweight predictions.

        print(&quot;Netlify function for weather collection executed.&quot;)
        return {
            &quot;statusCode&quot;: 200,
            &quot;body&quot;: json.dumps({&quot;message&quot;: &quot;Backend logic would run here.&quot;}),
        }
    except Exception as e:
        return {
            &quot;statusCode&quot;: 500,
            &quot;body&quot;: json.dumps({&quot;error&quot;: str(e)}),
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables in Netlify&lt;/strong&gt;: For the &lt;code&gt;OPENWEATHER_API_KEY&lt;/code&gt;, go to &lt;code&gt;Site settings &amp;gt; Build &amp;amp; deploy &amp;gt; Environment variables&lt;/code&gt; and add your key there.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Important Consideration&lt;/strong&gt;: For the Netlify function to persist changes directly to your GitHub repository (like committing the CSVs), you would need a more advanced setup. To maintain simplicity and direct storage in the Git repository with automatic CSV commits, the &lt;strong&gt;GitHub Actions solution remains the most straightforward and efficient for the data collector backend in this specific case&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-scaling-problem-and-the-necessary-architectural-pivot-&quot;&gt;The Scaling Problem (and the Necessary Architectural Pivot)&lt;/h4&gt;
&lt;p&gt;My initial idea was to monitor about 1000 cities and store everything in a single &lt;code&gt;weather_data.csv&lt;/code&gt; file. I did a quick calculation: 1000 cities &lt;em&gt; ~200 bytes/day &lt;/em&gt; 365 days * 3 years... over 200 MB! &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why is this a problem?&lt;/strong&gt; Because the frontend (our dashboard, which we'll see in the next post) runs in the user's browser. It would have to download that &lt;em&gt;entire&lt;/em&gt; 200 MB just to display the graph for &lt;em&gt;one&lt;/em&gt; city. Totally unacceptable in terms of performance. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Architectural Solution:&lt;/strong&gt; Switch to a &lt;strong&gt;&quot;one file per entity&quot;&lt;/strong&gt; strategy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We create a &lt;code&gt;data/&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;registrar_clima.py&lt;/code&gt; script now generates (or appends data to) one CSV file per city: &lt;code&gt;data/Madrid.csv&lt;/code&gt;, &lt;code&gt;data/Leon.csv&lt;/code&gt;, &lt;code&gt;data/Tokyo.csv&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This way, when the user wants to see the weather for Leon, the frontend will only download the &lt;code&gt;data/Leon.csv&lt;/code&gt; file, which will be just a few kilobytes. Instant loading! ✨&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second Scaling Obstacle (API Limits):&lt;/strong&gt; OpenWeatherMap, in its free plan, allows about 60 calls per minute. My loop to get data for 155 cities (my current list) would make these calls too quickly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vital Solution:&lt;/strong&gt; Add &lt;code&gt;import time&lt;/code&gt; at the beginning of the Python script and &lt;code&gt;time.sleep(1.1)&lt;/code&gt; at the end of the &lt;code&gt;for city in cities:&lt;/code&gt; loop. This introduces a pause of slightly more than 1 second between each API call, ensuring we stay below the limit and avoid being blocked. &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-part-1-&quot;&gt;Conclusion (Part 1)&lt;/h4&gt;
&lt;p&gt;We've got the foundation! We've built a robust and automated system that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connects to an external API securely.&lt;/li&gt;
&lt;li&gt;Processes and stores historical data for multiple entities (cities).&lt;/li&gt;
&lt;li&gt;Runs daily, at no cost, thanks to GitHub Actions.&lt;/li&gt;
&lt;li&gt;Is designed to scale efficiently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next post, we'll put on our frontend developer hats and build the interactive dashboard that will allow any user to explore this data with dynamic graphs. Don't miss it!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;references-and-links-of-interest-&quot;&gt;References and Links of Interest:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complete Web Service&lt;/strong&gt;: See the live project in action here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/weather/&quot;&gt;https://datalaria.com/apps/weather/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project GitHub Repository&lt;/strong&gt;: Explore the source code and project structure: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Dalaez/app_weather&quot;&gt;https://github.com/Dalaez/app_weather&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenWeatherMap&lt;/strong&gt;: Weather API documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://openweathermap.org/api&quot;&gt;https://openweathermap.org/api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python Requests&lt;/strong&gt;: Documentation for the HTTP requests library: &lt;a rel=&quot;nofollow&quot; href=&quot;https://requests.readthedocs.io/en/master/&quot;&gt;https://requests.readthedocs.io/en/master/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;: Official GitHub Actions guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions&quot;&gt;https://docs.github.com/en/actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netlify&lt;/strong&gt;: Official Netlify website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.netlify.com/&quot;&gt;https://www.netlify.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10040/weather-service-project-part-building-the-data-collector-python-github-actions-netlify</guid>
<pubDate>Tue, 20 Jan 2026 06:48:57 +0000</pubDate>
</item>
</channel>
</rss>