Serving static SvelteKit website from Minio bucket
A tutorial on how to serve static website created in SvelteKit from S3-compatible (Minio) bucket using NGINX webserver (SWAG).
Why S3 bucket?
Well, we might first ask a question, why would you even want to use S3 bucket to store your static website files? Especially considering, that the NGINX works so well serving files directly from filesystem?
Firstly, this was an interesting task to solve as it general solution for serving static websites from different S3-compatible bucket providers, be it Minio, B2, R2, etc.
Secondly, tools like Rclone provide super easy way to quickly sync build
command output folder with the bucket via command similar to
rclone sync ./build/ minio:static-website
and boom we have pushed our build to production. No SFTP-ing or other type of file copying needed, which I find very practical.
In this tutorial I will use Minio instance as my S3 provider as it is self-hostable and pretty lightweight and thus does not require expensive hardware.
Minio
For the purposes of this tutorial we will assume you have already properly installed and working Minio instance. If you do not have one yet, I would suggest using checking out official tutorial here.
Bucket and permissions
There are couple of possible ways to set up a new bucket and its permissions inside Minio. I will use Minio Console, which is the official Minio web user interface that comes with Minio Docker container preinstalled.
Once you’re in, we will create a new bucket by entering Administrator > Buckets
menu.
- As the name for the bucket you can choose whatever you prefer, for the purposes of this tutorial I will use
static-website
. You can even enable versioning or other features, but that is not neccessary. Do your own research to decide on wether you need them. - In the Anonymous tab for the newly created bucket add a new access rule with following settings:
Prefix: /
Access: readonly
- This will apply read only access to bucket, which means, that all files inside the bucket will be freely accessable to see and download, which we need to serve the website.
Now our S3 storage is prepared for serving the files we will upload in the next steps!
Static SvelteKit website
Even though this tutorial is based on the output structure of a SvelteKit project built using the adapter-static, the following steps can be slightly adjusted to server any kind of file structure.
First, inside svelte.config.js
I will specify option for a fallback page error.html
, which can be served, when a
requested page on your website does not exist, aka, 404. The name of this page does not matter and can be chosen as you wish.
import adapter from '@sveltejs/adapter-static';
...
const config = {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'error.html'
}),
...
},
...
}
Afterwards, we can build our project, which by default will be located in the newly created build
folder
at the root of our project.
Pushing build to production
I also want to add an extra command push
to the package.json
, which will enable quickly syncing the output
from build output directory ./build
to the S3 bucket where we will be serving our files from.
For this to work refer
to rclone and how to set up a new remote. In my case, I have already set up a remote called minio
. Therefore, the
command looks like this.
"scripts": {
...
"build": "vite build",
+ "push": "rclone sync ./build/ minio:static-website",
...
}
Afterwards, I can run it with npm
or yarn
, e.g., via yarn push
. This synchronizes the files from our build output
directory ./build
with the bucket we created earlier.
Webserver
As my webserver and entrypoint I am using slightly modified/preconfigured NGINX server - SWAG. Furthermore, the S3 store Minio is running as docker container named minio
and connected to the same network, where the NGINX docker container is running.
NGINX settings
Here is the location block for the defined server, which points to the static website.
location / {
...
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
set $upstream_app minio;
set $upstream_port 9000;
set $upstream_proto http;
set $bucket static-website;
rewrite ^/$ /$bucket/index.html break;
rewrite ^(/api/.*)$ /$bucket/$1 break;
rewrite ^([^.]+)$ /$bucket/$1.html break;
rewrite ^ /$bucket$uri break;
proxy_intercept_errors on;
error_page 403 404 /error.html;
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
}
Here is an explanation on what each setting in the NGINX configuration does.
Firstly:
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
are aforementioned SWAG-specific settings. proxy.conf
contains default NGINX proxying settings including set headers.
resolver.conf
is likewise SWAG-specific and contains NGINX resolver settings, to enable resolving the IP address of
our minio
container.
Next, we define couple of variables, which wil help us redirect the requests to the correct container, port and bucket.
Keep in mind to adjust the $bucket
variable with the name of the bucket you chose earlier.
set $upstream_app minio;
set $upstream_port 9000;
set $upstream_proto http;
set $bucket static-website;
Then we will have to rewrite the requests / requested URI to correctly point to the correct files inside our bucket.
This is exactly the part where the structure of your static website matters and following rules are Svelte specific.
The break
flags at the end of the rewrite is very important and the order matters!
rewrite ^/$ /$bucket/index.html break;
This will redirect /
requests to the index.html
inside our bucket.
rewrite ^(/api/.*)$ /$bucket/$1 break;
This will redirect all /api/*
requests to the bucket as is! That is needed, because in my website, the files under
./build/api/
have no file extensions.
rewrite ^([^.]+)$ /$bucket/$1.html break;
This will redirect all files without an extension to the bucket by appending .html
at the end. That is relevant for
example if we request path /about
and want about.html
to be served.
rewrite ^ /$bucket$uri break;
Is the catch-all rewrite, which will pass all other requests not rewritten above to the bucket as is.
Conclusion
After doing all this, we should be able to see our website being served from the NGINX server! I hope you found this tutorial helpful!