Deploying a static website is usually simpler than deploying a backend application. A website built with HTML, CSS, and JavaScript does not need a database connection, application server, or complex backend infrastructure. It only needs a web server that can deliver files to visitors.
However, deployment can become confusing when developers try to host a static website on a platform traditionally used for backend applications. They may encounter missing build directories, incorrect start commands, routing errors, or deployment failures because the platform expects a running web process.
Heroku supports static website deployment through its Front-End Web Cloud Native Buildpack. This allows developers to serve static files without writing a custom Express or PHP server.
In this article, we will build a static website, configure the required files, deploy it to Heroku, and troubleshoot common problems. We will also look at what happens when the website uses a frontend framework such as React or Vite.
What Is a Static Website?
A static website delivers files directly to the browser without generating HTML through server-side application code for every request.
A typical static website contains three main technologies:
Technology | Responsibility |
|---|---|
HTML | Defines the structure and content of a webpage. |
CSS | Controls layout, colors, typography, and responsiveness. |
JavaScript | Adds browser-side interactions and functionality. |
For example, consider a company website containing a homepage, an About page, a Services page, and a Contact page.
If these pages are already written as HTML files, the server does not need to execute backend code to generate them.
A static website can be useful for:
Personal portfolios.
Company websites.
Product landing pages.
Documentation websites.
Marketing pages.
Frontend applications that consume external APIs.
However, static websites have limitations. They cannot securely access private database credentials or perform server-side operations directly. Those responsibilities must be handled by a backend service.
How Static Website Hosting Works on Heroku
Heroku traditionally runs applications through processes defined in a Procfile or through platform-specific buildpack configuration.
For static websites, the Front-End Web Cloud Native Buildpack provides a web server that delivers the generated frontend files.
The deployment process follows this structure:
HTML, CSS, JavaScript
|
v
Git Repository
|
v
Heroku Build Process
|
v
Static Web Buildpack
|
v
Static Web Server
|
v
BrowserThe buildpack identifies the directory containing the website files and prepares the application to serve them.
For a basic website, developers do not need to create a separate Node.js server just to return HTML files.
One important distinction is that the buildpack serves static content. It does not automatically provide backend functionality, database operations, or server-side rendering.
Creating a Static Website Project
Let's create a simple website using HTML, CSS, and JavaScript.
Start by creating a project directory:
mkdir heroku-static-site
cd heroku-static-siteCreate the following directory structure:
heroku-static-site/
│
├── public/
│ ├── index.html
│ ├── about.html
│ ├── style.css
│ └── app.js
│
└── project.tomlThe public directory contains the files that the web server will deliver to visitors.
Creating the Homepage
Create a file named index.html inside the public directory.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>My Static Website</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header>
<h1>Welcome to My Website</h1>
<p>A simple website built with HTML, CSS, and JavaScript.</p>
</header>
<main>
<section>
<h2>About This Website</h2>
<p>
This is a static website that does not require
a backend application server.
</p>
<button id="welcomeButton">
Click Me
</button>
<p id="message"></p>
</section>
<a href="/about.html">About Us</a>
</main>
<script src="/app.js"></script>
</body>
</html>The page contains a heading, description, button, and navigation link.
The CSS and JavaScript files are loaded directly by the browser.
Adding CSS
Create style.css:
body {
margin: 0;
font-family: Arial, sans-serif;
background: #f4f6f8;
color: #333;
}
header {
background: #354b9c;
color: white;
padding: 40px 20px;
text-align: center;
}
main {
max-width: 900px;
margin: 40px auto;
padding: 20px;
background: white;
border-radius: 8px;
}
button {
padding: 10px 20px;
border: none;
background: #354b9c;
color: white;
cursor: pointer;
border-radius: 5px;
}This stylesheet controls the appearance of the website.
Adding JavaScript
Create app.js:
const button = document.getElementById("welcomeButton");
const message = document.getElementById("message");
button.addEventListener("click", () => {
message.textContent = "The website is working correctly!";
});This code adds a browser-side interaction.
There is no need to run an Express application because the JavaScript executes in the visitor's browser.
Configuring the Heroku Static Web Buildpack
After creating the website, configure the buildpack to identify the directory containing the static files.
Create a project.toml file in the root directory.
[_]
schema-version = "0.2"
[[io.buildpacks.group]]
id = "heroku/static-web-server"
[com.heroku.static-web-server]
root = "public"
index = "index.html"There are two important configuration properties.
The root property identifies the directory containing the website files.
The index property identifies the default HTML document returned when someone opens the website's root address.
In this example, Heroku serves files from the public directory and uses index.html as the homepage.
A common mistake is setting the root directory to the project directory instead of the directory containing the actual website files. This can cause missing pages or unexpected file responses.
Deploying the Website to Heroku
Once the project is ready, prepare the deployment environment.
Step 1: Install and Configure Heroku CLI
Install the Heroku CLI and authenticate:
heroku loginVerify that authentication is successful before continuing.
Step 2: Create a Heroku Application
Create an application:
heroku create my-static-siteUse an available application name.
Step 3: Initialize Git
If the project is not already a Git repository, initialize it:
git initAdd the project files:
git add .Create a commit:
git commit -m "Add static website"Step 4: Deploy the Application
Push the application to Heroku:
git push heroku mainDuring deployment, Heroku reads the buildpack configuration and prepares the static web server.
If your local branch is named master, use the appropriate Git push command for that branch.
Step 5: Open the Website
After deployment, open the application:
heroku open --app my-static-siteThe homepage should now be available through the application's Heroku address.
If the website does not load, inspect the deployment logs:
heroku logs --tail --app my-static-siteThe logs help identify build failures, startup problems, and missing configuration.
Deploying React and Vite Applications
A static website does not have to contain manually written HTML files.
Modern frontend frameworks generate static files during their production build process.
For example, a React application created with Vite usually generates its production output inside a directory named dist.
The source code may contain JSX, components, and development dependencies, but the final output consists of HTML, CSS, and JavaScript files.
Understanding the Build Process
Consider this project structure:
react-website/
│
├── src/
│ ├── App.jsx
│ └── main.jsx
│
├── public/
├── package.json
├── package-lock.json
└── project.tomlThe build command generates the production files.
npm install
npm run buildAfter the build completes, the directory structure generally looks like this:
dist/
├── index.html
└── assets/
├── index-abc123.js
└── index-def456.cssThese generated files are what the static web server needs to deliver.
Configure the Buildpacks
For a frontend project that requires Node.js during the build, configure the Node.js buildpack before the static web server buildpack.
Update project.toml:
[_]
schema-version = "0.2"
[[io.buildpacks.group]]
id = "heroku/nodejs"
[[io.buildpacks.group]]
id = "heroku/static-web-server"
[com.heroku.static-web-server]
root = "dist"
index = "index.html"The Node.js buildpack handles dependency installation and the frontend build process. The static web server then serves the generated files.
The order matters because the static server needs the production output generated by the frontend build.
For reproducible deployments, commit your package lockfile and make sure the required build script exists in package.json.
Handling 404 Errors in Single-Page Applications
One common problem with React and other single-page applications is direct URL navigation.
For example, your application may support these routes:
/
/about
/products
/contactWhen users navigate inside the application, the JavaScript router handles these paths.
However, when someone directly opens /products, the web server may search for a physical file named products.
If that file does not exist, the server can return a 404 response.
For a single-page application, configure the server to return the main HTML document for application routes.
[com.heroku.static-web-server.not-found]
status = 200
path = "/index.html"This allows the frontend router to process the requested path.
Use this fallback only when appropriate. If every missing file also returns the homepage, genuine missing JavaScript, CSS, or image files may become harder to identify.
Test both application routes and missing static assets after deployment.
Configuring Environment-Specific API Endpoints
A static website may need to communicate with a backend API.
For example, a frontend application might use one API endpoint in development and another in production.
A simple approach is to use a runtime configuration mechanism supported by the static web server.
For example, the HTML can contain a configurable attribute:
<html data-api-url="{{API_URL}}">The application can read that value using JavaScript:
const apiUrl =
document.documentElement.dataset.apiUrl;
async function loadProducts() {
const response = await fetch(
`${apiUrl}/products`
);
if (!response.ok) {
throw new Error("Unable to load products");
}
return await response.json();
}The API endpoint must be configured using the static web server's supported runtime templating configuration.
Do not assume that setting a Heroku environment variable automatically replaces text inside HTML files. Verify that the runtime configuration is enabled and configured correctly.
Also remember that values exposed to frontend JavaScript are public. Never put database passwords, private API keys, or other confidential credentials in browser-delivered configuration.
Troubleshooting Common Deployment Problems
Problem | Possible reason | What to check |
|---|---|---|
Homepage returns 404 | Incorrect root directory or missing index file | Verify |
CSS files are missing | Incorrect asset paths | Check HTML references and generated files |
JavaScript does not load | Build output or asset path problem | Inspect browser developer tools |
Deployment build fails | Missing dependencies or build configuration | Review Heroku build logs |
React routes return 404 | SPA fallback is missing | Configure the Not Found fallback |
API requests fail | Incorrect endpoint or CORS configuration | Check API configuration and browser network logs |
Old website content appears | Browser or asset caching | Review cache-control headers and asset filenames |
Checking Generated Files Before Deployment
For frontend applications, always verify the production output locally.
npm ci
npm run buildThen inspect the generated directory:
ls -la distConfirm that index.html exists and that its referenced assets are available.
This simple check can prevent many deployment failures.
When Should You Use Heroku for Static Websites?
Heroku is one possible hosting option, but it is not automatically the right choice for every static website.
The decision depends on your existing infrastructure, deployment workflow, operational requirements, and expected traffic.
Requirement | Heroku static hosting | Dedicated static hosting |
|---|---|---|
Existing Heroku infrastructure | Fits an existing deployment workflow | Requires a separate platform |
Simple HTML website | Supported | Usually straightforward |
Frontend build process | Supported through buildpacks | Supported by many providers |
Backend integration | Requires a separate backend service | Usually requires a separate backend service |
Custom server-side logic | Not provided by the static web server | Depends on the hosting platform |
Cost considerations | Includes dyno and platform costs | Depends on provider and usage model |
Operational management | Uses Heroku application workflows | Depends on the hosting provider |
If your organization already deploys backend applications on Heroku, hosting a static frontend on the same platform may simplify infrastructure management.
For a small portfolio or documentation website that only needs static file delivery, a dedicated static hosting platform may be more economical and require less operational configuration.
Evaluate the actual hosting requirements instead of choosing a platform only because it supports a particular deployment method.
Best Practices for Static Website Deployment
Keep source files separate from generated production files.
Commit the package lockfile when using frontend build tools.
Verify the build output before deploying.
Configure the correct static root directory.
Use appropriate fallback rules for single-page applications.
Keep private credentials on the backend.
Review caching behavior after every frontend deployment.
Test direct navigation, missing pages, and asset loading.
Inspect deployment logs when a build or startup fails.
Keep the frontend and backend responsibilities separate.
Advantages and Disadvantages
Advantages
Developers can deploy plain HTML, CSS, and JavaScript without creating a custom backend server.
Frontend build tools can generate production assets during deployment.
Existing Heroku deployment workflows can be reused.
Single-page application routing can be configured.
Runtime configuration can support different deployment environments.
Disadvantages
The static web server cannot execute custom backend application logic.
Frontend buildpack configuration requires attention to directory structure and execution order.
Dedicated static hosting may be more economical for simple websites.
Applications requiring server-side rendering need a different deployment architecture.
Incorrect caching and routing configuration can create problems after deployment.
Summary
Hosting static HTML, CSS, and JavaScript websites on Heroku does not require building a custom Express server just to deliver frontend files.
The Front-End Web Cloud Native Buildpack provides a static web server that can serve an existing website directory or the production output generated by frontend build tools.
For a basic website, the main configuration involves selecting the correct static root and index file. For React or Vite applications, the Node.js buildpack must generate the production assets before the static web server serves them.
Developers should pay particular attention to SPA routing, asset paths, runtime configuration, caching, and deployment logs.
Before choosing Heroku, also consider whether your application actually benefits from running on the platform. For teams already using Heroku, it can fit naturally into their deployment workflow. For a website that only needs simple static file delivery, a dedicated static hosting service may be a better option.


Join the conversation! Your thoughts help the community grow.