Static Service
The Static service provides a way to serve static content through a pre-configured Nginx setup. It balances simplicity with the flexibility needed for modern web applications.
Quick Start​
Add a Static service to your project by including this in your zerops.yaml
:
Default Behavior​
Every Static service in Zerops comes with built-in defaults optimized for modern web applications, including Single Page Applications. By default, for any incoming request, the service will:
- Try to serve the exact path (
$uri
) - Try with .html extension (
$uri.html
) - Look for an index.html in the directory (
$uri/index.html
) - Fall back to
/index.html
(suitable for SPAs) - Return 404 if none of the above exist
Single Page Applications work out of the box without any additional configuration. The built-in fallback to /index.html
ensures that client-side routing functions properly.
Routing Configuration​
The Static service allows you to configure additional URL routing and redirects through simple YAML configuration, abstracting away the complexity of Nginx configuration.
Custom Routing Configuration​
Configure custom routing beyond the default behavior in the run.routing
section of your zerops.yaml
:
Redirect Types​
Relative Redirects​
Remember that SPA routing is already built into the default behavior. You don't need to add any custom redirects for client-side routing to work.
Use relative redirects to route paths within your application. When both from
and to
are relative paths, you can omit the status
code to create a masked redirect that shows the content of the target page while preserving the original URL:
routing:
redirects:
# Masked redirect - URL stays the same but shows content from about-us
- from: /about
to: /about-us
# Standard redirect with status code
- from: /old-page
to: /new-page
status: 301
# Preserve the path when redirecting between directories
- from: /blog/*
to: /articles/
preservePath: true
status: 302
# Preserve both path and query parameters
- from: /posts/*
to: /blog/
preservePath: true
preserveQuery: true
status: 302
When using preservePath
with wildcards, ensure the to
path ends with a /
to maintain proper path concatenation. For example, /blog/*
to /new-blog/
will correctly redirect /blog/hello.html
to /new-blog/hello.html
, while /new-blog
would result in /new-bloghello.html
.
Absolute Redirects​
For redirecting between domains or to external URLs, use absolute redirects by including http://
or https://
. When using absolute URLs in either from
or to
, you must specify a status
code:
routing:
redirects:
# Redirect an old domain to a new one
- from: https://old-domain.com/*
to: https://new-domain.com
status: 301
preserveQuery: true # Optional: maintain query parameters
# Redirect with path preservation
- from: https://old-site.com/*
to: https://new-site.com/
status: 301
preservePath: true
Advanced Routing Features​
Wildcard Matching​
Use *
as a wildcard in your paths:
- At the end of a path: Matches any subsequent content
- At the start of a domain (after
https://
): Enables regex matching for subdomains
Example of domain management:
Matching Priority​
When multiple redirects are configured, they follow Nginx's matching priority system:
- Exact matches are checked first
- Simple path matches (without wildcards) are checked next
- Pattern matches (with wildcards) are checked last
For example:
In this configuration:
/
will redirect to/home
with a 302 status/about
will show content from/about-us
but keep the URL as/about
/blog/post-123.html
will redirect to/articles/post-123.html
- Any other path will use the default behavior
CORS Configuration​
You can enable CORS for your static service by adding a cors
directive:
The cors
directive sets the following headers:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Expose-Headers
The cors
directive has a special case: if you specify just "*"
, it's automatically converted to '*'
. For any other values, you need to include the proper Nginx syntax including quotes.
Custom Headers​
For more control over HTTP headers, use the headers
directive:
Header values are inserted directly into the Nginx configuration without additional quotes, which means:
- All values must include their own quotes (typically single quotes)
- If you need single quotes inside your header value, you must escape them in YAML (using double single quotes)
- To include the
always
directive, add it after your quoted value - For complex values, you can use YAML's block scalar notation (
>-
) for better readability
Here are examples for different header scenarios:
headers:
- for: "/"
values:
# Simple header with proper quoting
X-Frame-Options: "'DENY'"
# Header with 'always' directive
X-XSS-Protection: "'1; mode=block' always"
# Header with internal single quotes - need double single quotes for escaping
Content-Security-Policy: '"default-src ''self'' https://cdn.example.com"'
# Complex header with block scalar notation for better readability
Content-Security-Policy: >-
"default-src 'self' https://cdn.example.com;
script-src 'self' 'unsafe-inline';
img-src * data:" always
When this configuration is processed, it translates to the following Nginx directives:
add_header X-Frame-Options 'DENY';
add_header X-XSS-Protection '1; mode=block' always;
add_header Content-Security-Policy "default-src 'self' https://cdn.example.com";
add_header Content-Security-Policy "default-src 'self' https://cdn.example.com; script-src 'self' 'unsafe-inline'; img-src * data:" always;
When you specify headers for a path that doesn't have an existing location block, the Static service automatically creates a location with the same default behavior as the root path (trying files in order: $uri
, $uri.html
, $uri/index.html
, /index.html
or returning 404).
If you add headers for a path that already has a location block, your headers will be merged with the existing configuration.
Combining CORS and Custom Headers​
You can use both CORS and custom headers together:
The cors
directive sets default Access-Control headers for all routes, while the headers
directive allows you to set additional headers for specific paths.
If you specify Access-Control headers in the headers
directive, they will override the ones set by cors
for that specific path.
Prerender Integration​
The Static service includes built-in support for Prerender.io for server-side rendering for search engines and social media crawlers.
Basic Prerender Setup​
- Set the
PRERENDER_TOKEN
secret variable with your Prerender.io token - The service automatically configures necessary rewrites based on user agents
Custom Prerender Host​
If you're using a custom Prerender host, add it to environment variables in zerops.yaml
:
The default host is service.prerender.io
if not specified.
Advanced Configuration​
Switching to Full Nginx​
If you need more control over your Nginx configuration:
- Go to your Static service overview in the UI
- Click the three vertical dots in the left panel
- Select Need to switch to full Nginx service?
- Copy the generated Nginx configuration
- Use this configuration as a starting point for a full Nginx service
This allows you to move to a more customizable setup while maintaining your existing routing logic.
Best Practices​
-
Domain Migration
zerops.yamlUse permanent (301) redirects when permanently moving content to maintain SEO value.
-
Complex Redirects
-
Security Headers Add security headers to protect your application:
Frontend Framework Integration​
The Static service works with modern frontend frameworks. It can serve built static files from any framework with options for custom routing and Prerender.io integration if needed.
Example: Analog App Deployment​
Here's a configuration for deploying an Analog application:
This configuration:
- Uses Node.js 20 for building the application
- Installs dependencies with pnpm
- Builds the application
- Deploys the resulting static files to the Static service
You can enhance this basic setup with:
- Custom redirects for URL management
- Prerender.io integration for SEO
- Additional routing rules as needed
Common Configurations​
Multiple Domain Management​
run:
routing:
redirects:
# Product-specific domain
- from: https://product-promo.com/*
to: https://main-site.com/products
status: 302
# Campaign domain
- from: https://special-offer.com/*
to: https://main-site.com/campaign
status: 302
# Legacy domains and subdomains
- from: https://*.legacy-domain.com/*
to: https://main-site.com
status: 302