Single-Page Apps vs SSR: How Deployxa Automatically Optimizes Hardware Sizing | Deployxa

SPAs and SSR apps have different resource profiles, and Deployxa's intelligence service sizes them automatically. Here is how it works and why it saves you money.

← Back to Dispatch Articles
Engineering Log

Single-Page Apps vs SSR: How Deployxa Automatically Optimizes Hardware Sizing

SPAs and SSR apps have different resource profiles, and Deployxa's intelligence service sizes them automatically. Here is how it works and why it saves you money.

Single-Page Apps vs SSR: How Deployxa Optimizes Hardware Sizing

Not all web apps have the same resource profile. A single-page app (SPA) built with Vite and React is mostly static assets served from a CDN-like origin, with minimal server-side computation. A server-side rendered (SSR) app built with Next.js or Nuxt does significant server-side work on every request: rendering React components to HTML, fetching data from APIs, executing server actions. The two have fundamentally different CPU, memory, and concurrency profiles, and sizing them identically is a waste of money. Deployxa's intelligence service inspects your app's structure and automatically sizes the container appropriately: 0.5 CPU for SPAs that just serve static assets, 1.0 CPU for SSR apps that do real server work. Here is how it works and why it matters.

The direct answer is that Deployxa's intelligence service inspects your repository during the ingestion phase and classifies your app as one of several types: static SPA, dynamic SPA (with a Node server), SSR app, API server, or worker. Based on the classification, it assigns a default resource profile: 0.5 CPU and 512MB RAM for static and lightweight apps, 1.0 CPU and 1GB RAM for SSR and compute-heavy apps. You can override these defaults, but for most apps, the automatic sizing is correct and saves you from over-provisioning.

Why Hardware Sizing Matters

Hardware sizing matters for two reasons: cost and performance. Over-provisioning (giving your app more CPU and memory than it needs) wastes money, because you pay for resources you do not use. Under-provisioning (giving your app less than it needs) degrades performance, because the app runs out of memory or CPU under load. The right sizing balances cost and performance, giving the app exactly what it needs to handle its expected traffic.

For vibe coders, hardware sizing is a particularly thorny problem, because they typically do not know what their app needs. A vibe coder who built a landing page with Cursor does not know whether it needs 0.5 CPU or 2 CPU. They guess (or accept the platform's default), and they either overpay or underperform. Deployxa's automatic sizing removes this guesswork, by inspecting the app's structure and applying sensible defaults based on the framework and app type.

A second reason sizing matters: noisy-neighbor avoidance. On platforms that share CPU between containers (like some Kubernetes-based PaaSes), an under-sized app may steal CPU from neighbors during traffic spikes, which degrades everyone. Deployxa's bare-metal containers with cgroup-enforced CPU limits do not have this problem, because the cgroup prevents a container from exceeding its allocation. But the inverse problem (over-provisioning) still wastes money, and the intelligence service addresses it.

How the Intelligence Service Classifies Apps

The intelligence service inspects several signals to classify your app:

1. Framework detection

The service looks at your package.json, requirements.txt, go.mod, or other manifest files to identify the framework. Next.js, Nuxt, and SvelteKit are classified as SSR-capable (even if you are using them in SPA mode, the runtime can do SSR). Vite, Create React App, and Vue CLI are classified as SPAs. FastAPI, Express, and Go's net/http are classified as API servers.

2. Build output analysis

For Next.js apps, the service checks whether the build output includes SSR pages (the .next/server directory) or only static pages (the out directory). If only static pages are present, the app is effectively an SPA, even though it is built with Next.js. If SSR pages are present, the app needs a running Node server.

3. Server file detection

The service looks for server entry points (server.js, server.ts, app.js, app.py, main.go). If a server file is present, the app is dynamic and needs a running process. If only static assets are present (HTML, CSS, JS in a dist or build directory), the app is static and can be served from a lightweight static file server.

4. Configuration analysis

The service checks your framework's configuration for SSR settings. Next.js's output: 'export' in next.config.js means the app is static. Nuxt's ssr: false in nuxt.config.js means the app is in SPA mode. These settings override the framework's default behavior.

The Classification Decision Tree

The full decision tree, in pseudocode:

if framework == "nextjs":
    if next.config.js has output: 'export':
        return "static_spa"
    elif .next/server/ exists after build:
        return "ssr_app"
    else:
        return "dynamic_spa"
elif framework in ["vite", "cra", "vue-cli"]:
    if server.js or app.js exists:
        return "dynamic_spa"
    else:
        return "static_spa"
elif framework in ["nuxt", "sveltekit"]:
    if nuxt.config.js has ssr: false:
        return "static_spa"
    else:
        return "ssr_app"
elif framework in ["fastapi", "express", "gin", "echo"]:
    return "api_server"
elif framework in ["bullmq", "celery"]:
    return "worker"
else:
    return "dynamic_spa"  # default

The decision tree is conservative: when in doubt, it classifies as dynamic_spa (which gets the lower resource profile), because over-provisioning is wasted money but under-provisioning degrades performance. If the app turns out to need more, the doctor report will show high CPU/memory usage, and you can scale up.

The Default Resource Profiles

Based on the classification, the intelligence service assigns one of the following default resource profiles:

Static SPA (0.5 CPU, 512MB RAM)

For apps that are purely static (Vite + React, Next.js with output: 'export', etc.), the container just serves static files. The CPU and memory requirements are minimal, because there is no server-side computation. A 0.5 CPU container with 512MB RAM is more than enough to serve thousands of concurrent users.

Dynamic SPA (0.5 CPU, 512MB RAM)

For apps that have a lightweight Node server (e.g., an Express server serving an SPA with a few API routes), the resource requirements are still modest. The server does minimal work, and most of the traffic is static assets. A 0.5 CPU container with 512MB RAM is sufficient.

SSR App (1.0 CPU, 1GB RAM)

For SSR apps (Next.js with SSR, Nuxt with SSR), the server does significant work on every request: rendering React components, fetching data, executing server actions. The CPU and memory requirements are higher, because each request involves real computation. A 1.0 CPU container with 1GB RAM is the default, which can be scaled up for high-traffic apps.

API Server (1.0 CPU, 1GB RAM)

For API servers (FastAPI, Express, Go APIs), the resource requirements depend on the workload. For most APIs, 1.0 CPU and 1GB RAM is a good default, which can be scaled up for compute-heavy endpoints.

Worker (1.0 CPU, 1GB RAM)

For background workers (BullMQ, Celery, Go workers), the resource requirements depend on the workload. The default is 1.0 CPU and 1GB RAM, which can be scaled up for memory-heavy workers.

How the Profiles Map to the Free and Paid Tiers

The free tier offers 512MB RAM per app, which matches the SPA profiles but is below the SSR/API/Worker default of 1GB. On the free tier, SSR/API/Worker apps run with 512MB RAM (the maximum on free), which may be tight for memory-heavy workloads. On the paid tier ($9/month for 15 apps), the full 1GB default is available, and you can scale up further (2GB, 4GB, 8GB) for an additional pro-rated charge.

Step-by-Step: How Automatic Sizing Works in Practice

Here is what happens when you deploy a typical Next.js SSR app.

Step 1: Push your app

git push origin main

Step 2: Watch the ingestion log

The intelligence service inspects your repository:

[ingest] Detected Node.js project
[ingest] Framework: nextjs
[ingest] SSR detected: yes (next.config.js does not set output: 'export')
[ingest] Build output: .next/server/ directory present
[ingest] Classification: SSR app
[ingest] Default resource profile: 1.0 CPU, 1GB RAM
[ingest] Container spec generated

Step 3: Deploy

The container is provisioned with 1.0 CPU and 1GB RAM, which is appropriate for an SSR app. The build runs, the container starts, and the app is live.

Step 4: Override if needed

If you know your app needs more (or less) resources, you can override the defaults in the Deployxa dashboard. For example, if your SSR app does heavy data processing, you might scale up to 2.0 CPU and 2GB RAM. If your app has very low traffic, you might scale down to 0.5 CPU and 512MB RAM.

Step 5: Monitor and adjust

Use deployxa doctor to monitor your app's resource usage over time. If CPU or memory usage is consistently high, scale up. If it is consistently low, scale down. The automatic sizing gives you a good starting point, but the actual needs depend on your traffic patterns and workload.

Common Pitfalls

Three pitfalls appear in automatic sizing. First, misclassification of Next.js apps. A Next.js app with output: 'export' is correctly classified as static, but if you later remove that flag and add SSR pages without redeploying, the classification is stale. The intelligence service re-runs on every deploy, so this self-corrects on the next push. Second, memory-heavy SPAs. A Vite SPA that loads a large WASM module (e.g., a PDF viewer, a video editor) may need more than 512MB RAM, even though it is "static". The intelligence service does not detect WASM size, so you must override manually. Third, CPU-throttled workers. A background worker that does CPU-intensive work (e.g., image processing) may need more than 1.0 CPU. The intelligence service classifies it as "worker" and assigns 1.0 CPU, but the actual need may be 2.0 or 4.0 CPU. Monitor the doctor report and scale up.

Troubleshooting: Diagnosing Sizing Issues

Below are common sizing-related symptoms and their interpretations.

deployxa doctor shows: "Memory usage: 94% of 512MB"

The app is close to OOM. Either scale up the container, or profile the app for memory leaks. For Node.js apps, use --inspect and Chrome DevTools to take a heap snapshot.

deployxa doctor shows: "CPU usage: 92% avg over 5 min"

The app is CPU-bound. Either scale up, or optimize the hot path. For Next.js SSR apps, common causes are expensive getServerSideProps functions and large component trees.

deployxa doctor shows: "Container restarted 3 times in last 10 min"

The container is crash-looping, likely due to OOM kills. Check the logs for exit code 137, which indicates OOM. Scale up the memory or fix the memory leak.

App is slow under load, but doctor shows low CPU/memory

The bottleneck is not CPU or memory; it is likely the database or an external API. Check the doctor's database connectivity check, and use application-level tracing to identify slow queries.

The Cost Savings: Right-Sizing vs Over-Provisioning

The cost difference between right-sizing and over-provisioning is significant. If you run a static SPA on a 2.0 CPU, 2GB RAM container, you are paying for 4x more resources than you need, and the app performs no better than it would on a 0.5 CPU, 512MB RAM container. Over a year, this adds up to significant wasted spend.

Deployxa's automatic sizing eliminates this waste, by giving each app the resources it actually needs based on its structure. For a fleet of 10 apps, the savings can be hundreds of dollars per month, compared to a flat "2 CPU, 2GB RAM for everything" default that many platforms use.

Concrete Cost Comparison

Below is a concrete cost comparison for a fleet of 10 apps (5 SPAs, 3 SSR, 2 API servers) on different platforms.

| Platform | Sizing model | Monthly cost for 10 apps |

|---|---|---|

| Deployxa with automatic sizing | 0.5 CPU/512MB for SPAs, 1.0 CPU/1GB for SSR/API | $9 flat (paid tier, all 10 apps fit in 15-app limit) |

| Deployxa without sizing (over-provisioned to 1.0 CPU/1GB for all) | 1.0 CPU/1GB for all | $9 flat (same, because pricing is per-app not per-resource on paid tier) |

| Render with manual sizing | $7/app for starter, $25/app for standard | 5 starter + 5 standard = $160/month |

| Railway with manual sizing | $5/app base + usage | ~$50-100/month depending on usage |

| Heroku with manual sizing | $7-25/dyno | 10 basic dynos = $50-250/month |

Deployxa's flat pricing on the paid tier means the cost is the same regardless of sizing, but the right-sizing still matters for performance (an over-provisioned app on Deployxa is not more expensive, but an under-provisioned app on Deployxa is slower). On platforms that charge per-resource (Render, Railway, Heroku), right-sizing directly reduces cost.

When to Override the Defaults

The automatic sizing is a good default, but there are cases where you should override it:

1. High-traffic apps

If your app has high traffic, you might need more resources than the default. Monitor CPU and memory usage, and scale up if the app is hitting resource limits.

2. Compute-heavy apps

If your app does heavy computation (image processing, video transcoding, ML inference), you might need more CPU. The default 1.0 CPU might not be enough, and you should scale up to 2.0 or 4.0 CPU.

3. Memory-heavy apps

If your app loads large models into memory (like a Hugging Face transformer), you might need more RAM. The default 1GB might not be enough, and you should scale up to 2GB or 4GB.

4. Low-traffic apps

If your app has very low traffic, you might be able to scale down below the default. A small API server with 10 requests per day might be fine on 0.25 CPU and 256MB RAM.

When Automatic Sizing Is Not the Right Choice

Automatic sizing is not the right choice when: your app's resource needs vary dramatically over time (use horizontal scaling instead, with multiple smaller containers), your app has strict latency requirements that require over-provisioning (e.g., a trading app that must respond in under 50ms even under load), your app has compliance requirements that mandate specific resource limits (e.g., a regulated workload that must run on exactly 2 CPU and 4GB RAM per the auditor's specification), or your app is a benchmark target where you need to control variables (use a fixed profile to eliminate sizing as a confound). For these cases, override the automatic sizing and configure the container manually.

Conclusion: Right-Size Automatically, Override Manually

Hardware sizing is a thorny problem that vibe coders should not have to solve manually. Deployxa's intelligence service inspects your app's structure and assigns a sensible default resource profile, based on the framework, build output, and server configuration. The default is correct for most apps, and you can override it when you know better. The result is that you pay for the resources you actually need, without over-provisioning or under-provisioning.

Ready to deploy with automatic sizing? Drag your project to Deployxa Drop for an instant live preview, or install the CLI with npm i -g @deployxa/cli and deploy from your terminal. For more on deployment optimization, see our free developer tools and read about the 14-point readiness engine.

Ready to deploy with Deployxa?

Deploy your apps globally with automatic SSL and AI diagnostics.

Start Free Now