<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Debug The Why]]></title><description><![CDATA[Debug The Why]]></description><link>https://blog.talhasattar.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 12:26:42 GMT</lastBuildDate><atom:link href="https://blog.talhasattar.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Whitelisting an API With a Static IP Using Nginx]]></title><description><![CDATA[The Problem
We integrate with SuperControl, a property management API. Their security model is the old-school kind: they don't issue API keys you carry in a header. Instead, you give them a list of IP]]></description><link>https://blog.talhasattar.dev/whitelisting-an-api-with-a-static-ip-using-nginx</link><guid isPermaLink="true">https://blog.talhasattar.dev/whitelisting-an-api-with-a-static-ip-using-nginx</guid><dc:creator><![CDATA[Talha Sattar]]></dc:creator><pubDate>Wed, 13 May 2026 01:02:31 GMT</pubDate><content:encoded><![CDATA[<hr />
<h2>The Problem</h2>
<p>We integrate with <strong>SuperControl</strong>, a property management API. Their security model is the old-school kind: they don't issue API keys you carry in a header. Instead, you give them a list of IP addresses, and they only accept requests coming from those IPs. Anything else gets dropped at the edge.</p>
<p>That works perfectly if your backend is one server with one IP. It falls apart the moment you deploy on Vercel.</p>
<p>Vercel runs your code on serverless functions. Every invocation can come from a different machine in a different data center, with a different outbound IP. The whole pool changes over time — they add new edges, retire old ones, scale up during traffic spikes. There's no published list of stable IPs to whitelist, and even if there were, it would be huge and changing.</p>
<p>So I had two options. Either ask SuperControl to whitelist "the internet" (not going to happen), or put something in the middle that has <em>one</em> IP that never changes.</p>
<h2>The Managed Alternatives (and Why I Didn't Use Them)</h2>
<p>This problem is common enough that every major platform has a paid product for it. The question is whether the price tag matches the integration.</p>
<ul>
<li><p><strong>Vercel Static IPs</strong> — $100/month per project on the Pro plan, plus regional Private Data Transfer fees on top. Available on Pro and Enterprise; not on Hobby.</p>
</li>
<li><p><strong>AWS NAT Gateway</strong> — \(0.045/hour (~\)32.85/month) per gateway, plus \(0.045/GB processed, plus standard data transfer out. A single-AZ setup at low traffic lands around \)35–40/month; multi-AZ production setups easily run $100+ before data charges.</p>
</li>
<li><p><strong>Third-party proxies like QuotaGuard</strong> — around $19/month for entry tiers. Cheaper than the cloud-native options, but it's still a subscription to a black box.</p>
</li>
</ul>
<p>All of them solve the problem. Justifiable at scale, when the cost is a rounding error against engineering hours saved. Overkill for a single integration where the upstream API itself doesn't justify enterprise spend.</p>
<p>The DIY answer: a $5/month VPS from any provider (Hetzner, DigitalOcean, Vultr) with a permanent IP and Nginx installed. Same outcome, a fraction of the cost. The trade-off is that you're now responsible for keeping the box patched and the cert renewed — Certbot's auto-renewal handles the second part; weekly <code>apt update &amp;&amp; apt upgrade</code> handles the first.</p>
<p>For a single low-volume integration with one upstream, the trade is worth it. The moment I'm running ten of these or doing high-volume traffic, I'd reconsider.</p>
<h2>Quick Detour: What's a Reverse Proxy? What's Nginx?</h2>
<p>Skip this section if you already know.</p>
<p>A <strong>reverse proxy</strong> is a server that sits in front of another server and forwards traffic to it. The client thinks it's talking to the proxy; the proxy actually talks to the real backend on the client's behalf. "Reverse" because a normal proxy hides the client from the server (think VPN), while a reverse proxy hides the server from the client. Reverse proxies are how big sites do load balancing, caching, SSL termination, rate limiting, and — in our case — IP consolidation.</p>
<p><strong>Nginx</strong> (pronounced "engine-x") is the software you run on a server to make it a reverse proxy. It's one of the most-used web servers on the internet, free, fast, and configured through plain text files. You describe what should happen to incoming requests, reload it, and it does that. No code, just rules.</p>
<p>Put together: I'm running Nginx on a VPS, configured so that requests hitting my proxy domain get forwarded to SuperControl's API.</p>
<h2>The Solution</h2>
<pre><code class="language-plaintext">App on Vercel (dynamic IPs)
            ↓
Nginx on VPS (one static IP — whitelisted with SuperControl)
            ↓
SuperControl API
</code></pre>
<p>The VPS costs a few dollars a month and has a fixed IP. I gave that one IP to SuperControl. Every API call from the app now hits the VPS first, the VPS forwards it upstream, and SuperControl sees the same trusted address every time.</p>
<p>The catch: that subdomain is now publicly reachable. If I left it open, anyone who guessed the URL could use my VPS as a free, pre-authorized gateway into SuperControl's API. So the proxy needs its own auth layer — a shared secret in a custom header that the app sends and the VPS verifies before forwarding anything.</p>
<h2>The Config</h2>
<pre><code class="language-nginx">server {
    listen 443 ssl;
    server_name proxy.example.com;

    # Certbot-managed SSL certs here

    location = /health {
        return 200 "ok\n";
    }

    location / {
        if ($http_x_proxy_auth_key != "REPLACE_WITH_SECRET") {
            return 401;
        }

        proxy_pass https://api.upstream.example.com;
        proxy_ssl_server_name on;

        proxy_set_header Host api.upstream.example.com;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header x-proxy-auth-key "";
    }
}

server {
    listen 80;
    server_name proxy.example.com;
    return 301 https://\(host\)request_uri;
}
</code></pre>
<p>Forty lines doing six things:</p>
<ol>
<li><p><strong>HTTPS termination</strong> with a Certbot/Let's Encrypt cert, and HTTP→HTTPS redirect for anything that knocks on port 80.</p>
</li>
<li><p><code>/health</code> as an exact-match endpoint for uptime monitors — no auth, no logging noise, just <code>200 ok</code>.</p>
</li>
<li><p><strong>Auth check</strong> on every other path: if the <code>x-proxy-auth-key</code> header doesn't match the shared secret, return 401 and forward nothing.</p>
</li>
<li><p><code>proxy_pass</code> forwards the request to SuperControl, preserving the path, query string, method, and body.</p>
</li>
<li><p><code>Host</code> <strong>header rewrite</strong> so the upstream sees its own domain instead of our proxy subdomain — most APIs reject requests with the wrong <code>Host</code>.</p>
</li>
<li><p><strong>Strip the secret</strong> before forwarding. The auth key was for us; SuperControl has no business seeing it.</p>
</li>
</ol>
<h2>Three Things Worth Knowing</h2>
<p><strong>The header-to-variable rule.</strong> Nginx exposes every incoming header as a variable. The conversion: lowercase, replace hyphens with underscores, prefix with <code>\(http_</code>. So <code>x-proxy-auth-key</code> becomes <code>\)http_x_proxy_auth_key</code>, <code>Authorization</code> becomes <code>\(http_authorization</code>, <code>Content-Type</code> becomes <code>\)http_content_type</code>. Once you know the rule, every header is readable inside the config.</p>
<p><code>location = /path</code> <strong>vs</strong> <code>location /path</code><strong>.</strong> The <code>=</code> makes it an exact match — <code>/health</code> and nothing else. Without <code>=</code>, it's a prefix match, so <code>/</code> catches everything underneath it. This isn't just style: exact matches are checked first and short-circuit the rest of the routing, which is why they're the right choice for things like health checks.</p>
<p><code>proxy_ssl_server_name on</code> is the gotcha that ate an hour of my time. SuperControl, like most modern APIs, uses SNI to pick which certificate to present during the TLS handshake. Without this directive, Nginx opens the connection without telling the upstream which hostname it's asking for, and the handshake fails with errors that look like generic 502s. The fix is one line; finding it isn't.</p>
<h2>The Test Loop</h2>
<pre><code class="language-bash">sudo nginx -t                          # validate config syntax
sudo systemctl reload nginx            # apply if valid

# Health check — should always return 200
curl -i https://proxy.example.com/health

# Authed call — should return whatever the upstream returns
curl -i https://proxy.example.com/endpoint \
  -H "x-proxy-auth-key: REPLACE_WITH_SECRET"

# No header — should return 401, never touch the upstream
curl -i https://proxy.example.com/endpoint
</code></pre>
<p><code>nginx -t</code> is the most important habit. It validates the whole file before you reload — catches misplaced braces, missing semicolons, and unreachable blocks before they take down a live config. Never reload without it.</p>
<h2>Takeaway</h2>
<p>Vercel and friends abstract away IPs, DNS, SSL, and routing so well that you can forget the network layer exists — until a third-party constraint forces you back into it. Forty lines of Nginx and a $5 VPS turned an unsolvable problem into a solved one. The skill isn't memorizing the syntax; it's recognizing when the abstraction has run out and being willing to drop a layer down to fix it.</p>
]]></content:encoded></item><item><title><![CDATA[Stop Guessing. Here's When to Use SSR, CSR, SSG and ISR in Next.js]]></title><description><![CDATA[The web is evolving fast. New frameworks are shipping optimized solutions every month to make the web faster and more secure for everyone. But with all these options comes confusion especially for beg]]></description><link>https://blog.talhasattar.dev/stop-guessing-here-s-when-to-use-ssr-csr-ssg-and-isr-in-next-js</link><guid isPermaLink="true">https://blog.talhasattar.dev/stop-guessing-here-s-when-to-use-ssr-csr-ssg-and-isr-in-next-js</guid><dc:creator><![CDATA[Talha Sattar]]></dc:creator><pubDate>Thu, 09 Apr 2026 01:43:11 GMT</pubDate><content:encoded><![CDATA[<p>The web is evolving fast. New frameworks are shipping optimized solutions every month to make the web faster and more secure for everyone. But with all these options comes confusion especially for beginners.</p>
<p>Next.js alone gives you SSR, CSR, SSG and ISR. Four different rendering strategies. Each one sounds great until you try to figure out which one your project actually needs.</p>
<p>I'm going to break all of them down. Not just what they are but when to use each one and how they affect your SEO because sometimes what looks like the obvious choice is actually the wrong one for your situation.</p>
<p>Let's get into it.</p>
<h2>SSR: Server-Side Rendering</h2>
<p>When a user visits a page that uses SSR the server runs your React code right at that moment. It generates the full HTML and sends it to the browser. The user sees a complete page almost instantly.</p>
<p>But here's the thing. That page isn't interactive yet. It looks ready but buttons don't work and forms don't respond. In the background React is "hydrating" the page which means it's attaching all the event listeners and state management to the HTML that's already on screen. Once hydration finishes everything becomes interactive.</p>
<p>The psychology behind this matters. Users perceive your site as fast because they see content immediately. The brief moment before hydration completes is almost never noticed.</p>
<p>The two metrics that matter for SSR are First Contentful Paint (how quickly the user sees something) and Time to Interactive (how quickly they can actually use it). SSR optimizes heavily for the first one.</p>
<p><strong>SEO impact:</strong> SSR is great for SEO. When Google's crawler hits your page it gets fully rendered HTML with all the content right there. No waiting for JavaScript to execute. Your meta tags, headings, text content and structured data are all present in the initial response. This is why most content heavy sites that care about search rankings go with SSR.</p>
<p>One tradeoff to know: you don't have access to browser APIs like window or localStorage during SSR because the code runs on the server. If your component needs those you'll need to handle that with client-side checks or move that logic to a client component.</p>
<h2>CSR: Client-Side Rendering</h2>
<p>CSR is the traditional React approach. The browser downloads your JavaScript bundle then the V8 engine executes it and React builds the entire page on the client side.</p>
<p>This means two things matter more than anything: the user's device performance and their network speed.</p>
<p>Here's where it gets interesting. Think about who your users actually are.</p>
<p>If you're building a luxury helicopter rental platform for clients in New York those users almost certainly have the latest iPhones fast home internet and 5G connections. For them CSR and SSR will feel almost identical in speed. The JavaScript bundle downloads in milliseconds on their connection and their device processes it instantly.</p>
<p>Now imagine you're building a government services portal used by people across rural areas with older phones and slower networks. That same JavaScript bundle that loaded instantly in New York might take 5 to 8 seconds to download and another few seconds to execute on a budget Android device. Suddenly SSR isn't just a nice optimization. It's the difference between a usable site and one that people abandon.</p>
<p>The rendering strategy you choose should be based on who is using your product not just what the technology can do.</p>
<p><strong>SEO impact:</strong> CSR is the worst option for SEO. When Google's crawler visits a CSR page it initially sees an empty div or a loading spinner. Google can execute JavaScript and eventually see your content but it's a second pass and not guaranteed. Your pages may get indexed slower or with missing content. If you don't care about SEO like an admin dashboard or internal tool then CSR is totally fine. But if you need organic traffic from search don't use CSR for those pages.</p>
<h2>SSG: Static Site Generation</h2>
<p>SSG generates your pages at build time. When you run <code>next build</code> it pre-renders every page into static HTML files. These files sit on a CDN and when someone visits they just get served instantly. No server processing. No waiting.</p>
<p>This is the fastest option because there's literally nothing to compute at request time. The HTML already exists.</p>
<p>Use SSG when the content doesn't change frequently and isn't different per user. Think marketing pages, blog posts, documentation, landing pages and about pages. Your content is the same for everyone and it only changes when you deploy.</p>
<p>The downside is obvious. If your data changes you need to rebuild and redeploy. For a blog with 50 posts that's fine. For an e-commerce site with 100,000 products that update prices every hour that's not practical.</p>
<p><strong>SEO impact:</strong> SSG is the best option for SEO. The HTML is pre-built and sitting on a CDN so Google gets it instantly. Page speed is as fast as it gets which Google directly uses as a ranking factor. Your content is fully rendered with all meta tags and structured data baked in. If your pages don't need to change per user and you want the best possible search rankings SSG is the answer.</p>
<h2>ISR: Incremental Static Regeneration</h2>
<p>ISR is the hybrid approach that tries to give you the speed of SSG with the freshness of SSR.</p>
<p>It works like this. You statically generate the page at build time just like SSG. But you set a revalidation time. After that time passes the next visitor triggers a background regeneration of the page. They still get the cached version instantly but the next visitor after them gets the freshly regenerated page.</p>
<p>Think of it as SSG with an expiration date.</p>
<p>This is perfect for content that changes but not in real time. Product pages where prices update daily. A news site where articles are published every few hours. A dashboard that shows data that refreshes every 30 minutes. The content needs to be relatively fresh but it doesn't need to be live to the second.</p>
<p><strong>SEO impact:</strong> ISR gives you almost the same SEO benefits as SSG. The page is pre-rendered so Google sees full HTML on the first crawl. And because the page regenerates in the background your content stays fresh for subsequent crawls without sacrificing speed. This is the sweet spot for sites that need both good SEO and regularly updated content. E-commerce product pages and news sites use this a lot.</p>
<h2>So Which One Do You Actually Use?</h2>
<p>Here's the decision framework:</p>
<p><strong>Use SSG</strong> when your content rarely changes and is the same for all users. Blogs. Docs. Marketing sites. Best SEO. Fastest performance. This should be your default starting point.</p>
<p><strong>Use ISR</strong> when your content changes periodically but doesn't need to be real-time. Product catalogs. News articles. Nearly as good for SEO as SSG but your data stays fresh.</p>
<p><strong>Use SSR</strong> when the content is different for every user or every request. Personalized dashboards. Search results. Pages that depend on cookies or authentication. Great for SEO when you need dynamic content that search engines should still index.</p>
<p><strong>Use CSR</strong> when the page is behind authentication anyway and SEO doesn't matter. Admin panels. Internal tools. Complex interactive apps where the initial load time is less important than the runtime experience.</p>
<h2>Quick Reference</h2>
<table>
<thead>
<tr>
<th>Strategy</th>
<th>Built When</th>
<th>SEO</th>
<th>Speed</th>
<th>Best For</th>
</tr>
</thead>
<tbody><tr>
<td>SSG</td>
<td>Deploy time</td>
<td>Best</td>
<td>Fastest</td>
<td>Blogs docs landing pages</td>
</tr>
<tr>
<td>ISR</td>
<td>Deploy + revalidates</td>
<td>Great</td>
<td>Fast</td>
<td>Products news catalogs</td>
</tr>
<tr>
<td>SSR</td>
<td>Every request</td>
<td>Great</td>
<td>Good</td>
<td>Personalized dynamic pages</td>
</tr>
<tr>
<td>CSR</td>
<td>In browser</td>
<td>Poor</td>
<td>Depends on device</td>
<td>Admin panels internal tools</td>
</tr>
</tbody></table>
<h2>The Real Lesson</h2>
<p>The framework gives you these options. It doesn't tell you which one to pick. That's your job as an engineer. Understanding your users their devices their networks your SEO requirements and your content update frequency is what makes the difference.</p>
<p>A rendering strategy isn't a technical decision. It's a product decision.</p>
<p>And that's the kind of thinking that no tutorial and no AI tool is going to do for you.</p>
]]></content:encoded></item></channel></rss>