{"id":3119,"date":"2025-12-07T17:07:04","date_gmt":"2025-12-07T14:07:04","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/why-put-the-spa-and-api-on-one-domain\/"},"modified":"2025-12-07T17:07:04","modified_gmt":"2025-12-07T14:07:04","slug":"why-put-the-spa-and-api-on-one-domain","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/why-put-the-spa-and-api-on-one-domain\/","title":{"rendered":"Why Put the SPA and API on One Domain?"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>{<br \/>\n  &#8220;title&#8221;: &#8220;Hosting React, Vue and Angular SPAs with One API Domain on Nginx&#8221;,<br \/>\n  &#8220;content&#8221;: &#8220;<\/p>\n<p>Modern front\u2011end stacks like React, Vue and Angular make it very easy to build fast, interactive single page applications (SPAs). The tricky part often comes later: deciding how to host the built SPA and its backend API cleanly on one domain, with correct Nginx routing and a solid SSL\/TLS architecture. Developers frequently start with separate ports or domains during development, then run into CORS problems, SEO concerns, cookie issues and complex SSL setups when moving to production. In this article, we will walk through a practical, production\u2011ready way to host your SPA and API behind Nginx on a single domain, using clear URL routing and modern HTTPS. We will focus on static builds of React, Vue and Angular apps, a backend API (Node.js, PHP, Python, etc.), and Nginx acting as a reverse proxy and static file server. The goal is to help you ship a simple, predictable architecture that scales from a small <a href=\"https:\/\/www.dchost.com\/vps\">VPS<\/a> to larger dedicated or colocation setups at dchost.com without surprising you six months later.<\/p>\n<p>nnnn<\/p>\n<p>Separating the frontend (for example on <code>app.example.com<\/code>) and the API (for example on <code>api.example.com<\/code>) is a common pattern, but for many projects, using a single domain like <code>example.com<\/code> for everything is simpler and more robust. In our own projects and in customer environments we host at dchost.com, we see several recurring advantages when everything is served from one origin.<\/p>\n<p>nn<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><ul><li><a href=\"#1_No_CORS_Headaches\"><span class=\"toc_number toc_depth_2\">0.1<\/span> 1. No CORS Headaches<\/a><\/li><li><a href=\"#2_Simpler_SSLTLS_and_Certificates\"><span class=\"toc_number toc_depth_2\">0.2<\/span> 2. Simpler SSL\/TLS and Certificates<\/a><\/li><li><a href=\"#3_Cleaner_SEO_and_Analytics\"><span class=\"toc_number toc_depth_2\">0.3<\/span> 3. Cleaner SEO and Analytics<\/a><\/li><li><a href=\"#4_Fewer_Moving_Parts_for_Small_Teams\"><span class=\"toc_number toc_depth_2\">0.4<\/span> 4. Fewer Moving Parts for Small Teams<\/a><\/li><\/ul><\/li><li><a href=\"#HighLevel_Architecture_How_the_Pieces_Fit_Together\"><span class=\"toc_number toc_depth_1\">1<\/span> High\u2011Level Architecture: How the Pieces Fit Together<\/a><\/li><li><a href=\"#Preparing_Your_React_Vue_or_Angular_App_for_Nginx\"><span class=\"toc_number toc_depth_1\">2<\/span> Preparing Your React, Vue or Angular App for Nginx<\/a><ul><li><a href=\"#1_React_Create_React_App_Vite_Nextjs_SPA_mode\"><span class=\"toc_number toc_depth_2\">2.1<\/span> 1. React (Create React App, Vite, Next.js SPA mode)<\/a><\/li><li><a href=\"#2_Vue_Vue_CLI_Vite\"><span class=\"toc_number toc_depth_2\">2.2<\/span> 2. Vue (Vue CLI, Vite)<\/a><\/li><li><a href=\"#3_Angular\"><span class=\"toc_number toc_depth_2\">2.3<\/span> 3. Angular<\/a><\/li><li><a href=\"#Folder_Layout_on_the_Server\"><span class=\"toc_number toc_depth_2\">2.4<\/span> Folder Layout on the Server<\/a><\/li><\/ul><\/li><li><a href=\"#Nginx_Routing_for_SPAs_HTML5_History_Assets_and_Multiple_Apps\"><span class=\"toc_number toc_depth_1\">3<\/span> Nginx Routing for SPAs: HTML5 History, Assets and Multiple Apps<\/a><ul><li><a href=\"#Basic_Nginx_Server_Block_for_a_Single_SPA\"><span class=\"toc_number toc_depth_2\">3.1<\/span> Basic Nginx Server Block for a Single SPA<\/a><\/li><li><a href=\"#Handling_HTML5_History_Mode_Routes\"><span class=\"toc_number toc_depth_2\">3.2<\/span> Handling HTML5 History Mode Routes<\/a><\/li><li><a href=\"#Multiple_SPAs_Under_One_Domain\"><span class=\"toc_number toc_depth_2\">3.3<\/span> Multiple SPAs Under One Domain<\/a><\/li><\/ul><\/li><li><a href=\"#Proxying_the_API_Through_Nginx_and_Avoiding_CORS\"><span class=\"toc_number toc_depth_1\">4<\/span> Proxying the API Through Nginx and Avoiding CORS<\/a><ul><li><a href=\"#Basic_Reverse_Proxy_Setup\"><span class=\"toc_number toc_depth_2\">4.1<\/span> Basic Reverse Proxy Setup<\/a><\/li><li><a href=\"#When_Do_You_Still_Need_CORS\"><span class=\"toc_number toc_depth_2\">4.2<\/span> When Do You Still Need CORS?<\/a><\/li><li><a href=\"#Authentication_Cookies_and_SameSite\"><span class=\"toc_number toc_depth_2\">4.3<\/span> Authentication, Cookies and SameSite<\/a><\/li><\/ul><\/li><li><a href=\"#SSLTLS_Architecture_for_a_SPA_API_on_Nginx\"><span class=\"toc_number toc_depth_1\">5<\/span> SSL\/TLS Architecture for a SPA + API on Nginx<\/a><ul><li><a href=\"#1_Certificate_Strategy\"><span class=\"toc_number toc_depth_2\">5.1<\/span> 1. Certificate Strategy<\/a><\/li><li><a href=\"#2_A_Practical_Modern_TLS_Config\"><span class=\"toc_number toc_depth_2\">5.2<\/span> 2. A Practical Modern TLS Config<\/a><\/li><li><a href=\"#3_HTTP_to_HTTPS_Redirect\"><span class=\"toc_number toc_depth_2\">5.3<\/span> 3. HTTP to HTTPS Redirect<\/a><\/li><li><a href=\"#4_Internal_HTTP_Only_Between_Nginx_and_API\"><span class=\"toc_number toc_depth_2\">5.4<\/span> 4. Internal HTTP Only Between Nginx and API<\/a><\/li><\/ul><\/li><li><a href=\"#Deployment_Workflow_and_Environments\"><span class=\"toc_number toc_depth_1\">6<\/span> Deployment Workflow and Environments<\/a><ul><li><a href=\"#1_SPA_Deployment_Flow\"><span class=\"toc_number toc_depth_2\">6.1<\/span> 1. SPA Deployment Flow<\/a><\/li><li><a href=\"#2_API_Deployment_Flow\"><span class=\"toc_number toc_depth_2\">6.2<\/span> 2. API Deployment Flow<\/a><\/li><li><a href=\"#3_Keeping_Frontend_and_Backend_in_Sync\"><span class=\"toc_number toc_depth_2\">6.3<\/span> 3. Keeping Frontend and Backend in Sync<\/a><\/li><li><a href=\"#4_Staging_and_Production\"><span class=\"toc_number toc_depth_2\">6.4<\/span> 4. Staging and Production<\/a><\/li><\/ul><\/li><li><a href=\"#When_You_Might_Still_Separate_Domains_or_Subdomains\"><span class=\"toc_number toc_depth_1\">7<\/span> When You Might Still Separate Domains or Subdomains<\/a><\/li><li><a href=\"#Putting_It_All_Together\"><span class=\"toc_number toc_depth_1\">8<\/span> Putting It All Together<\/a><\/li><\/ul><\/div>\n<h3><span id=\"1_No_CORS_Headaches\">1. No CORS Headaches<\/span><\/h3>\n<p>n<\/p>\n<p>When the SPA HTML, JavaScript and API endpoints all live under the same scheme\/domain\/port (for example <code>https:\/\/example.com<\/code>), the browser treats them as the same origin. That means:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>No need to configure <code>Access-Control-Allow-Origin<\/code> for your own frontend.<\/li>\n<p>n  <\/p>\n<li>Fewer preflight OPTIONS requests and less room for subtle misconfigurations.<\/li>\n<p>n  <\/p>\n<li>Clean cookie handling (session or auth cookies stay first\u2011party).<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>This alone removes an entire class of support tickets we see: \u2018the API works in Postman, but not from the frontend.\u2019<\/p>\n<p>nn<\/p>\n<h3><span id=\"2_Simpler_SSLTLS_and_Certificates\">2. Simpler SSL\/TLS and Certificates<\/span><\/h3>\n<p>n<\/p>\n<p>With one domain, you usually need a single certificate covering <code>example.com<\/code> (optionally also <code>www.example.com<\/code>). You can terminate TLS on Nginx, then route plain HTTP internally to the SPA files and API service. This works especially well with automated SSL such as Let\u2019s Encrypt, where you can use a single ACME flow. If you want to dive deeper into certificate choices and automation, you can also check our article <a href='https:\/\/www.dchost.com\/blog\/en\/lets-encrypt-ile-ucretsiz-ssl-sertifikasi-kurulumu-cpanel-ve-directadminde-otomatik-yenileme-rehberi\/'>why free SSL with Let\u2019s Encrypt and auto\u2011renewal matters on modern hosting stacks<\/a>.<\/p>\n<p>nn<\/p>\n<h3><span id=\"3_Cleaner_SEO_and_Analytics\">3. Cleaner SEO and Analytics<\/span><\/h3>\n<p>n<\/p>\n<p>Search engines and analytics tools tend to be simpler to configure when everything lives under one domain. Even though SPAs need some care for rendering and meta tags, at least your canonical URLs live in one place and you do not split authority across multiple subdomains. If you are still planning your domain and DNS side, our guide on <a href='https:\/\/www.dchost.com\/blog\/en\/web-hosting-nedir-domain-dns-sunucu-ve-ssl-nasil-birlikte-calisir\/'>how domain, DNS, server and SSL fit together on a typical hosting setup<\/a> is a good background read.<\/p>\n<p>nn<\/p>\n<h3><span id=\"4_Fewer_Moving_Parts_for_Small_Teams\">4. Fewer Moving Parts for Small Teams<\/span><\/h3>\n<p>n<\/p>\n<p>For smaller teams and early\u2011stage projects, operating fewer domains and certificates simply reduces operational overhead. You have one Nginx virtual host to reason about, one deployment path and one SSL renewal process.<\/p>\n<p>nn<\/p>\n<h2><span id=\"HighLevel_Architecture_How_the_Pieces_Fit_Together\">High\u2011Level Architecture: How the Pieces Fit Together<\/span><\/h2>\n<p>nn<\/p>\n<p>Let\u2019s start with a minimal but realistic diagram for a single\u2011domain SPA + API stack:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li><strong>Nginx<\/strong> listening on ports 80 and 443 on <code>example.com<\/code>.<\/li>\n<p>n  <\/p>\n<li><strong>Static SPA build<\/strong> (React, Vue, Angular) located in a directory such as <code>\/var\/www\/spa<\/code>.<\/li>\n<p>n  <\/p>\n<li><strong>API backend<\/strong> running on an internal port, for example <code>127.0.0.1:3000<\/code> (Node.js) or <code>127.0.0.1:9000<\/code> (PHP\u2011FPM behind FastCGI), or a separate internal container.<\/li>\n<p>n  <\/p>\n<li><strong>Nginx routing<\/strong>:\n<ul>n        <\/p>\n<li><code>\/<\/code> and SPA routes like <code>\/dashboard<\/code>, <code>\/users\/42<\/code> \u2192 served from SPA <code>index.html<\/code> with <code>try_files<\/code>.<\/li>\n<p>n        <\/p>\n<li><code>\/assets\/<\/code> or <code>\/static\/<\/code> \u2192 served as static files.<\/li>\n<p>n        <\/p>\n<li><code>\/api\/<\/code> \u2192 proxied to backend API.<\/li>\n<p>n      <\/ul>\n<p>n  <\/li>\n<p>n  <\/p>\n<li><strong>Single TLS termination<\/strong> in Nginx with HTTP\/2 or HTTP\/3 enabled.<\/li>\n<p>n<\/ul>\n<p>nn<\/p>\n<p>You can host this stack on a Linux VPS, a <a href=\"https:\/\/www.dchost.com\/dedicated-server\">dedicated server<\/a> or your own hardware in colocation at dchost.com. The configuration itself looks almost identical across these environments; the main difference is how many CPU\/RAM\/IOPS you allocate and how you handle scaling, which we help many customers plan.<\/p>\n<p>nn<\/p>\n<h2><span id=\"Preparing_Your_React_Vue_or_Angular_App_for_Nginx\">Preparing Your React, Vue or Angular App for Nginx<\/span><\/h2>\n<p>nn<\/p>\n<p>On the frontend side, the key is to produce a static production build and ensure that client\u2011side routing plays nicely with Nginx. Let\u2019s briefly look at what each framework typically outputs and what Nginx expects.<\/p>\n<p>nn<\/p>\n<h3><span id=\"1_React_Create_React_App_Vite_Nextjs_SPA_mode\">1. React (Create React App, Vite, Next.js SPA mode)<\/span><\/h3>\n<p>n<\/p>\n<p>For a classic SPA (not server\u2011side rendered):<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li><strong>Create React App<\/strong>: <code>npm run build<\/code> outputs a <code>build\/<\/code> directory with <code>index.html<\/code>, <code>static\/<\/code> assets, hashed JS\/CSS.<\/li>\n<p>n  <\/p>\n<li><strong>Vite<\/strong> React template: <code>npm run build<\/code> outputs a <code>dist\/<\/code> directory with a similar structure.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>In both cases, you will copy the resulting directory to your server (for example <code>\/var\/www\/spa<\/code>) and configure Nginx to serve <code>index.html<\/code> for unknown paths. That\u2019s what enables URLs like <code>\/dashboard<\/code> to work with React Router in <code>browserHistory<\/code> mode.<\/p>\n<p>nn<\/p>\n<h3><span id=\"2_Vue_Vue_CLI_Vite\">2. Vue (Vue CLI, Vite)<\/span><\/h3>\n<p>n<\/p>\n<p>Vue behaves similarly:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li><strong>Vue CLI<\/strong>: <code>npm run build<\/code> \u2192 <code>dist\/<\/code><\/li>\n<p>n  <\/p>\n<li><strong>Vite Vue<\/strong>: <code>npm run build<\/code> \u2192 <code>dist\/<\/code><\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>Again, deploy the <code>dist\/<\/code> folder to Nginx and route SPA paths with <code>try_files<\/code>. If you use Vue Router in <code>history<\/code> mode, you must ensure that Nginx rewrites all non\u2011API, non\u2011asset requests back to <code>index.html<\/code>.<\/p>\n<p>nn<\/p>\n<h3><span id=\"3_Angular\">3. Angular<\/span><\/h3>\n<p>n<\/p>\n<p>For Angular:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>Run <code>ng build --configuration production<\/code> (or <code>--prod<\/code> on older versions).<\/li>\n<p>n  <\/p>\n<li>This outputs a <code>dist\/&lt;project-name&gt;\/<\/code> directory.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>Angular uses its own router for SPA navigation; the Nginx side is essentially the same: serve the built <code>index.html<\/code> and static assets, and let the framework handle routing in the browser.<\/p>\n<p>nn<\/p>\n<h3><span id=\"Folder_Layout_on_the_Server\">Folder Layout on the Server<\/span><\/h3>\n<p>n<\/p>\n<p>A simple layout we often use on VPS and dedicated servers at dchost.com looks like this:<\/p>\n<p>n<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">\/var\/www\/example.com\/n  spa\/           # React\/Vue\/Angular build outputn  api\/           # Optional API code (if not containerized)n  logs\/n  releases\/      # If you use a symlink-based deployment flown<\/code><\/pre>\n<p>n<\/p>\n<p>You can deploy new SPA versions by building locally or in CI, uploading to a new directory under <code>releases\/<\/code> and then atomically switching a symlink <code>spa -&gt; releases\/2025-12-07T10-30-00<\/code>. We describe this flow in detail in our guide on <a href='https:\/\/www.dchost.com\/blog\/en\/vpse-sifir-kesinti-ci-cd-nasil-kurulur-rsync-sembolik-surumler-ve-systemd-ile-sicacik-bir-yolculuk\/'>zero\u2011downtime CI\/CD to a VPS using rsync and symlinked releases<\/a>.<\/p>\n<p>nn<\/p>\n<h2><span id=\"Nginx_Routing_for_SPAs_HTML5_History_Assets_and_Multiple_Apps\">Nginx Routing for SPAs: HTML5 History, Assets and Multiple Apps<\/span><\/h2>\n<p>nn<\/p>\n<p>Nginx\u2019s job is to map different URL patterns to the right target: the SPA, static assets, or the backend API. The central directive is <code>try_files<\/code>, which checks whether a file exists on disk and, if not, falls back to your <code>index.html<\/code>.<\/p>\n<p>nn<\/p>\n<h3><span id=\"Basic_Nginx_Server_Block_for_a_Single_SPA\">Basic Nginx Server Block for a Single SPA<\/span><\/h3>\n<p>n<\/p>\n<p>Here is a simplified HTTPS server block (we will add SSL details later) for a React\/Vue\/Angular SPA at <code>example.com<\/code> with an <code>\/api<\/code> backend:<\/p>\n<p>nn<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">server {n    listen 80;n    server_name example.com www.example.com;nn    # Redirect all HTTP to HTTPSn    return 301 https:\/\/$host$request_uri;n}nnserver {n    listen 443 ssl http2;n    server_name example.com www.example.com;nn    root \/var\/www\/example.com\/spa;n    index index.html;nn    # SSL configuration will go here (certificates, protocols, ciphers)nn    # Serve static files directlyn    location \/assets\/ {n        try_files $uri =404;n    }nn    location \/static\/ {n        try_files $uri =404;n    }nn    # API requests are proxied to the backendn    location \/api\/ {n        proxy_pass http:\/\/127.0.0.1:3000\/;n        proxy_http_version 1.1;n        proxy_set_header Host $host;n        proxy_set_header X-Real-IP $remote_addr;n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;n        proxy_set_header X-Forwarded-Proto $scheme;n    }nn    # Everything else is handled by the SPAn    location \/ {n        try_files $uri $uri\/ \/index.html;n    }n}n<\/code><\/pre>\n<p>nn<\/p>\n<p>Key points:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li><strong><code>location \/api\/<\/code><\/strong> catches all API calls and forwards them to the backend server on <code>127.0.0.1:3000<\/code>.<\/li>\n<p>n  <\/p>\n<li><strong><code>location \/<\/code><\/strong> uses <code>try_files<\/code> to:\n<ul>n        <\/p>\n<li>Serve real files (<code>$uri<\/code>) when they exist (CSS, JS, images).<\/li>\n<p>n        <\/p>\n<li>Otherwise fall back to <code>\/index.html<\/code>, letting the SPA router handle the path.<\/li>\n<p>n      <\/ul>\n<p>n  <\/li>\n<p>n  <\/p>\n<li><strong>HTTP is redirected to HTTPS<\/strong> in a separate server block to enforce secure access.<\/li>\n<p>n<\/ul>\n<p>nn<\/p>\n<h3><span id=\"Handling_HTML5_History_Mode_Routes\">Handling HTML5 History Mode Routes<\/span><\/h3>\n<p>n<\/p>\n<p>For SPAs using HTML5 history mode (React Router <code>BrowserRouter<\/code>, Vue Router <code>history<\/code> mode, Angular default), direct navigation to <code>\/dashboard<\/code> or <code>\/users\/42<\/code> should load the same <code>index.html<\/code> file. The <code>try_files<\/code> line above handles exactly that scenario. If you forget this, Nginx will return 404 for every deep link.<\/p>\n<p>nn<\/p>\n<h3><span id=\"Multiple_SPAs_Under_One_Domain\">Multiple SPAs Under One Domain<\/span><\/h3>\n<p>n<\/p>\n<p>Sometimes you want multiple frontends under one domain, for example:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li><code>https:\/\/example.com\/<\/code> \u2192 marketing site (static or CMS).<\/li>\n<p>n  <\/p>\n<li><code>https:\/\/example.com\/app\/<\/code> \u2192 main React app.<\/li>\n<p>n  <\/p>\n<li><code>https:\/\/example.com\/admin\/<\/code> \u2192 separate Vue admin panel.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>You can isolate them by using different <code>location<\/code> blocks and <code>alias<\/code> roots:<\/p>\n<p>nn<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">root \/var\/www\/example.com\/public;  # Marketing site rootnnlocation \/app\/ {n    alias \/var\/www\/example.com\/spa-main\/;n    index index.html;n    try_files $uri $uri\/ \/app\/index.html;n}nnlocation \/admin\/ {n    alias \/var\/www\/example.com\/spa-admin\/;n    index index.html;n    try_files $uri $uri\/ \/admin\/index.html;n}n<\/code><\/pre>\n<p>nn<\/p>\n<p>Notes:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>Use <code>alias<\/code> rather than <code>root<\/code> inside nested locations so that <code>\/app\/<\/code> maps cleanly to your SPA directory.<\/li>\n<p>n  <\/p>\n<li>Ensure the built apps are configured with proper <code>base<\/code> paths (for Angular) or <code>publicPath<\/code>\/<code>base<\/code> options (for React\/Vue) so that asset URLs are correct under subdirectories.<\/li>\n<p>n<\/ul>\n<p>nn<\/p>\n<h2><span id=\"Proxying_the_API_Through_Nginx_and_Avoiding_CORS\">Proxying the API Through Nginx and Avoiding CORS<\/span><\/h2>\n<p>nn<\/p>\n<p>With the SPA and API under one domain, your frontend can call <code>\/api\/...<\/code> directly. Nginx will then forward the request to the backend service, which may run on the same server or another internal host.<\/p>\n<p>nn<\/p>\n<h3><span id=\"Basic_Reverse_Proxy_Setup\">Basic Reverse Proxy Setup<\/span><\/h3>\n<p>n<\/p>\n<p>The earlier example already showed a minimal <code>location \/api\/<\/code> block. Let\u2019s expand it slightly for better resilience:<\/p>\n<p>nn<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">upstream api_backend {n    server 127.0.0.1:3000;  # Could be multiple servers for load balancingn    keepalive 32;n}nnserver {n    # ... SSL, root, etc.nn    location \/api\/ {n        proxy_pass http:\/\/api_backend\/;n        proxy_http_version 1.1;nn        proxy_set_header Host $host;n        proxy_set_header X-Real-IP $remote_addr;n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;n        proxy_set_header X-Forwarded-Proto $scheme;nn        proxy_read_timeout 60s;n        proxy_connect_timeout 5s;nn        # Optional: limit upload sizen        client_max_body_size 10m;n    }n}n<\/code><\/pre>\n<p>nn<\/p>\n<p>Using an <code>upstream<\/code> block lets you add more API servers later without changing the SPA. Nginx will handle simple load balancing for you.<\/p>\n<p>nn<\/p>\n<h3><span id=\"When_Do_You_Still_Need_CORS\">When Do You Still Need CORS?<\/span><\/h3>\n<p>n<\/p>\n<p>Even with one domain, there are a few cases where CORS still matters:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>Your SPA consumes a third\u2011party API on a different domain.<\/li>\n<p>n  <\/p>\n<li>You host multiple products under different domains but share one API.<\/li>\n<p>n  <\/p>\n<li>Some internal tools run from a different origin (for example, internal dashboards).<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>In that case, you can configure CORS either in your backend application or directly in Nginx using <code>add_header<\/code> directives. For many internal or B2B apps, however, keeping your main SPA and main API on the same domain is the cleanest approach and saves you from a lot of complexity.<\/p>\n<p>nn<\/p>\n<h3><span id=\"Authentication_Cookies_and_SameSite\">Authentication, Cookies and SameSite<\/span><\/h3>\n<p>n<\/p>\n<p>When everything is on one domain, you can safely use first\u2011party cookies for authentication. You should still set modern flags such as <code>Secure<\/code>, <code>HttpOnly<\/code> and an appropriate <code>SameSite<\/code> value. If you want to dive deeper into security headers and cookies, our guide on <a href='https:\/\/www.dchost.com\/blog\/en\/http-guvenlik-basliklari-rehberi-hsts-csp-ve-digerlerini-ne-zaman-nasil-uygulamalisin\/'>HTTP security headers like HSTS and CSP<\/a> and our article about <a href='https:\/\/www.dchost.com\/blog\/en\/samesitelax-mi-strict-mi-secure-ve-httponly-ile-nginx-apachede-cerezleri-tertemiz-nasil-kurarsin\/'>configuring SameSite, Secure and HttpOnly cookies correctly on Nginx and Apache<\/a> are both good companions to this setup.<\/p>\n<p>nn<\/p>\n<h2><span id=\"SSLTLS_Architecture_for_a_SPA_API_on_Nginx\">SSL\/TLS Architecture for a SPA + API on Nginx<\/span><\/h2>\n<p>nn<\/p>\n<p>Now let\u2019s look at the HTTPS side. A clean TLS configuration improves security, browser compatibility and performance. Getting it right once saves you a lot of future work.<\/p>\n<p>nn<\/p>\n<h3><span id=\"1_Certificate_Strategy\">1. Certificate Strategy<\/span><\/h3>\n<p>n<\/p>\n<p>For a single domain SPA + API stack, you typically need:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>A certificate for <code>example.com<\/code> (CN and\/or SAN).<\/li>\n<p>n  <\/p>\n<li>Optionally also <code>www.example.com<\/code> in the same certificate.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>You can use a DV certificate from a commercial CA or a Let\u2019s Encrypt certificate obtained via ACME HTTP\u201101 or DNS\u201101 challenge. The important part is that Nginx has <code>ssl_certificate<\/code> and <code>ssl_certificate_key<\/code> paths pointing to the live certificate files and that you have automation in place to renew them. At dchost.com we help many customers configure automated ACME flows both on shared hosting panels and on VPS\/dedicated servers.<\/p>\n<p>nn<\/p>\n<h3><span id=\"2_A_Practical_Modern_TLS_Config\">2. A Practical Modern TLS Config<\/span><\/h3>\n<p>n<\/p>\n<p>Here is a condensed but realistic TLS setup for Nginx:<\/p>\n<p>nn<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">server {n    listen 443 ssl http2;n    server_name example.com www.example.com;nn    root \/var\/www\/example.com\/spa;n    index index.html;nn    ssl_certificate     \/etc\/letsencrypt\/live\/example.com\/fullchain.pem;n    ssl_certificate_key \/etc\/letsencrypt\/live\/example.com\/privkey.pem;nn    ssl_protocols TLSv1.2 TLSv1.3;n    ssl_prefer_server_ciphers on;nn    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:n                 ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:n                 ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';nn    ssl_session_timeout 1d;n    ssl_session_cache shared:SSL:10m;nn    ssl_stapling on;n    ssl_stapling_verify on;nn    add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains' always;nn    # ... locations for \/, \/api, \/assets etc.n}n<\/code><\/pre>\n<p>nn<\/p>\n<p>This setup:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>Enables TLS 1.2 and 1.3, which is the practical minimum in 2025.<\/li>\n<p>n  <\/p>\n<li>Enables OCSP stapling for better TLS performance and reliability.<\/li>\n<p>n  <\/p>\n<li>Adds HSTS to enforce HTTPS for future visits.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>For a deeper dive into what to change when TLS standards evolve, see our article on <a href='https:\/\/www.dchost.com\/blog\/en\/ssl-tls-protokol-guncellemeleri-modern-https-icin-net-yol-haritasi\/'>SSL\/TLS protocol updates and what to change on your servers and when<\/a>.<\/p>\n<p>nn<\/p>\n<h3><span id=\"3_HTTP_to_HTTPS_Redirect\">3. HTTP to HTTPS Redirect<\/span><\/h3>\n<p>n<\/p>\n<p>You almost always want to redirect all HTTP traffic to HTTPS to avoid serving mixed content or leaking sensitive data. The minimal server block for port 80 is:<\/p>\n<p>nn<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">server {n    listen 80;n    server_name example.com www.example.com;n    return 301 https:\/\/$host$request_uri;n}n<\/code><\/pre>\n<p>nn<\/p>\n<p>If you use Let\u2019s Encrypt with HTTP\u201101 challenges, you may additionally need to allow access to <code>\/.well-known\/acme-challenge\/<\/code> before redirecting, or you can terminate ACME on another path or use DNS\u201101 challenges. Plan that at the beginning so you do not accidentally block certificate renewal later.<\/p>\n<p>nn<\/p>\n<h3><span id=\"4_Internal_HTTP_Only_Between_Nginx_and_API\">4. Internal HTTP Only Between Nginx and API<\/span><\/h3>\n<p>n<\/p>\n<p>Inside the same server, it is perfectly fine to use plain HTTP between Nginx and the backend API (for example <code>127.0.0.1:3000<\/code>). The public traffic is fully encrypted up to Nginx; internal localhost traffic is not exposed to the internet. If you communicate with an API running on another machine or in another data center, consider mTLS or at least HTTPS between nodes, especially for sensitive data.<\/p>\n<p>nn<\/p>\n<h2><span id=\"Deployment_Workflow_and_Environments\">Deployment Workflow and Environments<\/span><\/h2>\n<p>nn<\/p>\n<p>A clean deployment flow is just as important as the initial architecture. You want to be able to update your SPA bundle or API code without downtime and without half\u2011deployed states where some users see the old frontend and others hit a new API version.<\/p>\n<p>nn<\/p>\n<h3><span id=\"1_SPA_Deployment_Flow\">1. SPA Deployment Flow<\/span><\/h3>\n<p>n<\/p>\n<p>A simple, robust flow we use often looks like this:<\/p>\n<p>n<\/p>\n<ol>n  <\/p>\n<li>Build the SPA in CI (for example a pipeline that runs <code>npm ci &amp;&amp; npm run build<\/code>).<\/li>\n<p>n  <\/p>\n<li>Upload the build directory to the server under a timestamped path (<code>releases\/2025-12-07T10-30-00\/<\/code>).<\/li>\n<p>n  <\/p>\n<li>Update a symlink <code>spa -&gt; releases\/2025-12-07T10-30-00<\/code>.<\/li>\n<p>n  <\/p>\n<li>Optionally reload Nginx (not strictly required if only static files changed).<\/li>\n<p>n<\/ol>\n<p>n<\/p>\n<p>This gives you an instant rollback option: repoint the symlink to the previous release and you are back to a known state.<\/p>\n<p>nn<\/p>\n<h3><span id=\"2_API_Deployment_Flow\">2. API Deployment Flow<\/span><\/h3>\n<p>n<\/p>\n<p>For the API, you typically have a long\u2011running process or pool:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>Node.js with a process manager (like systemd or PM2).<\/li>\n<p>n  <\/p>\n<li>PHP\u2011FPM pools for PHP frameworks like Laravel or Symfony.<\/li>\n<p>n  <\/p>\n<li>Python (Gunicorn\/Uvicorn) behind systemd.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>On a VPS or dedicated server, you can run your API as a systemd service listening on an internal port and let Nginx proxy to it. If you are deciding where to host your Node.js or similar backend code, our guide on <a href='https:\/\/www.dchost.com\/blog\/en\/node-js-uygulamalarini-nerede-host-etmeli-cpanel-paylasimli-hosting-ve-vps-karsilastirmasi\/'>hosting Node.js apps on shared hosting, cPanel or VPS<\/a> walks through realistic scenarios and trade\u2011offs.<\/p>\n<p>nn<\/p>\n<h3><span id=\"3_Keeping_Frontend_and_Backend_in_Sync\">3. Keeping Frontend and Backend in Sync<\/span><\/h3>\n<p>n<\/p>\n<p>One common concern is: what happens if the SPA and API versions do not match? Some practical tips:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li>Design your API to be backward compatible for at least one SPA release.<\/li>\n<p>n  <\/p>\n<li>Deploy the API first, then switch the SPA symlink to the new build.<\/li>\n<p>n  <\/p>\n<li>Use feature flags and environment variables rather than hard\u2011coding URLs.<\/li>\n<p>n  <\/p>\n<li>Version your API endpoints if you need to make breaking changes (<code>\/api\/v1\/<\/code>, <code>\/api\/v2\/<\/code>), and route them separately in Nginx.<\/li>\n<p>n<\/ul>\n<p>nn<\/p>\n<h3><span id=\"4_Staging_and_Production\">4. Staging and Production<\/span><\/h3>\n<p>n<\/p>\n<p>It is often worth having a staging environment like <code>staging.example.com<\/code> that mirrors your production Nginx config and TLS settings but uses a separate API database. This lets you test routing rules, new SPA builds and API updates against real\u2011looking infrastructure without affecting users. DNS and SSL for staging follow the same logic as production; if you are planning a new domain or subdomain for staging, our checklist for <a href='https:\/\/www.dchost.com\/blog\/en\/yeni-aldiginiz-alan-adini-hosting-hesabina-baglamak-adim-adim-nameserver-dns-ve-ssl-rehberi\/'>connecting a new domain to hosting with DNS and SSL<\/a> is a handy reference.<\/p>\n<p>nn<\/p>\n<h2><span id=\"When_You_Might_Still_Separate_Domains_or_Subdomains\">When You Might Still Separate Domains or Subdomains<\/span><\/h2>\n<p>nn<\/p>\n<p>While one domain is a great default, there are scenarios where splitting makes sense:<\/p>\n<p>n<\/p>\n<ul>n  <\/p>\n<li><strong>Multi\u2011tenant SaaS<\/strong> where each customer brings their own domain for the frontend but APIs stay on a vendor domain.<\/li>\n<p>n  <\/p>\n<li><strong>Very large organisations<\/strong> with separate teams, lifecycles and compliance requirements for frontend and backend.<\/li>\n<p>n  <\/p>\n<li><strong>Specialised CDN usage<\/strong> where you want to cache the SPA on a separate hostname with aggressive policies while the API remains more dynamic and constrained.<\/li>\n<p>n<\/ul>\n<p>n<\/p>\n<p>Even then, Nginx reverse proxy patterns, TLS best practices and deployment workflows remain largely the same. You just add more server blocks and possibly more certificates.<\/p>\n<p>nn<\/p>\n<h2><span id=\"Putting_It_All_Together\">Putting It All Together<\/span><\/h2>\n<p>nn<\/p>\n<p>Hosting React, Vue and Angular SPAs with an API on one domain is less about magic Nginx tricks and more about a clean, predictable structure: a static build served by Nginx, a well\u2011scoped <code>\/api\/<\/code> reverse proxy, and a modern TLS configuration that terminates HTTPS in one place. Once you decide that <code>https:\/\/example.com<\/code> is your single origin, the rest is mostly about mapping URL paths carefully and keeping your SSL\/TLS setup healthy and automated. From there, you can grow: move the API to a separate internal server, add a CDN in front of Nginx, or scale out with more VPS or dedicated nodes at dchost.com as traffic grows.<\/p>\n<p>nn<\/p>\n<p>If you are planning a new SPA or refactoring an existing stack, this is a good moment to also revisit your backup, monitoring and security posture. Our articles on <a href='https:\/\/www.dchost.com\/blog\/en\/3-2-1-yedekleme-stratejisi-neden-ise-yariyor-cpanel-plesk-ve-vpste-otomatik-yedekleri-nasil-kurarsin\/'>automating 3\u20112\u20111 backups on cPanel, Plesk and VPS<\/a> and on <a href='https:\/\/www.dchost.com\/blog\/en\/vps-sunucu-guvenligi-pratik-olceklenebilir-ve-dogrulanabilir-yaklasimlar\/'>practical VPS hardening against real\u2011world threats<\/a> complement the routing and SSL topics we covered here.<\/p>\n<p>nn<\/p>\n<p>At dchost.com we work with teams that range from a single developer on their first VPS to seasoned DevOps teams managing multiple dedicated or colocated servers. If you would like help designing or migrating your SPA + API architecture, or you simply want a stable home for your Nginx, TLS and database stack, our domain, hosting, VPS, dedicated and colocation services are built exactly for that. Start with one clean domain, one solid Nginx config and safe HTTPS \u2013 and grow from there without rewriting everything six months down the line.<\/p>\n<p>&#8220;,<br \/>\n  &#8220;focus_keyword&#8221;: &#8220;Hosting React Vue Angular SPA with Nginx routing and SSL&#8221;,<br \/>\n  &#8220;meta_description&#8221;: &#8220;Learn how to host React, Vue and Angular SPAs with an API on one domain using Nginx. Routing, proxying, SSL\/TLS and deployment architecture explained in depth.&#8221;,<br \/>\n  &#8220;faqs&#8221;: [<br \/>\n    {<br \/>\n      &#8220;question&#8221;: &#8220;Why is it better to host my React, Vue or Angular SPA and API on one domain?&#8221;,<br \/>\n      &#8220;answer&#8221;: &#8220;Putting your SPA and API on one domain keeps everything under a single browser origin (same scheme, domain and port). That means you avoid most CORS configuration, reduce preflight OPTIONS requests and simplify cookie\u2011based authentication because cookies remain first\u2011party. SSL\/TLS is also easier: you only need one certificate and one Nginx termination point. This reduces operational overhead, especially for small teams, and makes SEO and analytics configuration more straightforward because all URLs live under the same domain.&#8221;<br \/>\n    },<br \/>\n    {<br \/>\n      &#8220;question&#8221;: &#8220;How should I configure Nginx for a SPA using React Router, Vue Router or Angular routing?&#8221;,<br \/>\n      &#8220;answer&#8221;: &#8220;You should serve the built SPA as static files but use a fallback to index.html for unknown paths. In Nginx this usually means a server block with a root pointing to your build directory and a location like: &#8220;location \/ { try_files $uri $uri\/ \/index.html; }&#8221;. This allows deep links such as \/dashboard or \/users\/42 to work with HTML5 history mode. For APIs, create a separate location like \/api\/ that uses proxy_pass to forward requests to your backend service running on an internal port.&#8221;<br \/>\n    },<br \/>\n    {<br \/>\n      &#8220;question&#8221;: &#8220;How do I terminate SSL\/TLS for my SPA and API on Nginx?&#8221;,<br \/>\n      &#8220;answer&#8221;: &#8220;Terminate SSL\/TLS once at Nginx on port 443, then route traffic internally over HTTP to your SPA files and API backend. In the HTTPS server block, configure ssl_certificate and ssl_certificate_key to point to your certificate files (Let\u2019s Encrypt or commercial). Enable modern protocols (TLS 1.2 and 1.3) and features like OCSP stapling, and add HSTS for stricter HTTPS. A separate port 80 server block should simply redirect all traffic to HTTPS. The API itself does not need to handle TLS if it only accepts connections from Nginx on localhost or a private network.&#8221;<br \/>\n    },<br \/>\n    {<br \/>\n      &#8220;question&#8221;: &#8220;Can I host multiple SPAs (for example, app and admin) on the same domain behind Nginx?&#8221;,<br \/>\n      &#8220;answer&#8221;: &#8220;Yes. You can host multiple SPAs under different paths, such as \/app\/ and \/admin\/, by using dedicated Nginx location blocks with alias. For example, &#8220;location \/app\/ { alias \/var\/www\/example.com\/spa-main\/; try_files $uri $uri\/ \/app\/index.html; }&#8221;. Make sure your build configuration (publicPath or base href) matches the subdirectory so assets are loaded correctly. The API can still live under \/api\/, shared by both SPAs, keeping your TLS and backend infrastructure simple.&#8221;<br \/>\n    },<br \/>\n    {<br \/>\n      &#8220;question&#8221;: &#8220;What kind of server do I need to run this Nginx SPA + API setup?&#8221;,<br \/>\n      &#8220;answer&#8221;: &#8220;Technically you can run this architecture on anything from a small VPS to a powerful dedicated server or even your own colocated hardware, depending on traffic and resource needs. The key components are a Linux environment, Nginx, your chosen runtime for the API (for example Node.js, PHP\u2011FPM or Python) and enough CPU, RAM and disk IO to handle your workload. At dchost.com we usually recommend starting with a modest VPS for early projects and scaling up or out as your metrics grow, reusing the same Nginx routing and SSL patterns described in this article.&#8221;<br \/>\n    }<br \/>\n  ]<br \/>\n}<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>{ &#8220;title&#8221;: &#8220;Hosting React, Vue and Angular SPAs with One API Domain on Nginx&#8221;, &#8220;content&#8221;: &#8220; Modern front\u2011end stacks like React, Vue and Angular make it very easy to build fast, interactive single page applications (SPAs). The tricky part often comes later: deciding how to host the built SPA and its backend API cleanly on [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3120,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-3119","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/3119","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=3119"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/3119\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/3120"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=3119"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=3119"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=3119"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}