Build static HTML spaces
Frontend libraries like Svelte, React, or Observable Framework generate static webapps, but require a build step.
You generally have the following options to create a static HTML space.
- You can host the code on GitHub and deploy it through a GitHub action, or you can build and deploy it from your local machine.
- Alternatively, you can host the website in a Docker space, as I did for severo/followgraph. The Dockerfile builds the website on every push to the repository; then, the container serves the updated site at runtime. But it's not optimal, and the space will be stopped automatically after a time, which would not be the case with a static HTML space.
Here, we propose a hack with two spaces:
- the code space: a Docker space that hosts the application source code, builds it and deploys it, and
- the site space: a static HTML space that hosts the built website.
In this tutorial, we deploy the default Svelte demo page.
Thanks to @XciD for the idea and optimizations to the Dockerfile. Thanks to @Wauplin for the review and corrections 🙏.
Site space
Let's start by creating the site space that will host the built website. Go to https://huggingface.co/new-space. We call the space "svelte-demo" and select the "Static" SDK and the "Blank" Static template.
The space contains a README.md and default HTML and CSS files. See the initial state at: https://huggingface.co/spaces/severo/svelte-demo/tree/777cd50e4088a5deed54117d1ae6b561948db994
We don't need to work on this space anymore. It will be fully controlled from code space.
Code space
Let's now create another space that will contain the website's source code and the Dockerfile for building and deploying it.
Create the code space
We go to https://huggingface.co/new-space, call the space "build-svelte-demo," select the "Docker" SDK and then the "Blank" Docker template.
Clone the repository locally
We work locally by cloning this space to our machine:
$ git clone git@hf.co:spaces/severo/build-svelte-demo
$ cd build-svelte-demo
Create the Svelte website
As an example, let's create the Svelte demo page. We call vite
and select Svelte
as the framework with the Typescript
variant.
$ npm init vite
Need to install the following packages:
create-vite@5.4.0
Ok to proceed? (y) y
> npx
> create-vite
✔ Project name: … svelte-demo
✔ Select a framework: › Svelte
✔ Select a variant: › TypeScript
Scaffolding project in /home/slesage/hf/build-svelte-demo/svelte-demo...
Done. Now run:
cd svelte-demo
npm install
npm run dev
You can also use Vite to scaffold projects for vanilla javascript, other libraries such as Vue or React, or use a community template.
Ensure the website works locally
Install the packages and start the development web server:
$ cd svelte-demo
$ npm install
$ npm run dev
Open the browser at http://localhost:5173
. You should see the default Svelte page.
Create the Dockerfile
Copy the following code to a new file called Dockerfile
at the repository's root.
FROM ubuntu:22.04
# Install Node.js 22 - https://github.com/nodesource/distributions?tab=readme-ov-file#installation-instructions-deb
# And python3
RUN apt update \
&& apt install -y curl python3 python3-pip \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh \
&& bash nodesource_setup.sh \
&& apt update \
&& apt install -y nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& rm nodesource_setup.sh
RUN pip install --upgrade "huggingface_hub[cli]"
# Build the app
WORKDIR /tmp/app
COPY svelte-demo/ ./
RUN npm ci && npm rebuild && npm run build
# The site space name must be passed as an environment variable
# https://huggingface.co/docs/hub/spaces-sdks-docker#buildtime
ARG STATIC_SPACE
# The Hugging Face token must be passed as a secret (https://huggingface.co/docs/hub/spaces-sdks-docker#buildtime)
# 1. get README.md from the site space
RUN --mount=type=secret,id=HF_TOKEN,mode=0444,required=true \
huggingface-cli download --token=$(cat /run/secrets/HF_TOKEN) --repo-type=space --local-dir=/tmp/app/dist $STATIC_SPACE README.md && rm -rf /tmp/app/dist/.cache
# 2. upload the new build to the site space, including README.md
RUN --mount=type=secret,id=HF_TOKEN,mode=0444,required=true \
huggingface-cli upload --token=$(cat /run/secrets/HF_TOKEN) --repo-type=space $STATIC_SPACE /tmp/app/dist . --delete "*"
# Halt execution because the code space is not meant to run.
RUN exit 1
Some explanations:
- We use a generic image (Ubuntu) to install any tool we need. In this case, we use node and python, but you can easily adapt the docker file if you need to build using other tools.
- The first block installs Node.js, Python 3, and the huggingface_hub CLI.
- The next block installs the npm packages and builds the website to
/tmp/app/dist
. - We then declare that the
STATIC_SPACE
environment variable is required. It must be set to the repository name of the site space. More on that later. - The last block downloads the README.md file to the locally built app and then uploads everything to replace the current contents. Note that we pass a space secret called
HF_TOKEN
(see the following sections). - The last line (
RUN exit 1
) is a hack: we return an error code to prevent the code space from running.
Prepare the STATIC_SPACE
environment variable
Enter the code space settings, and set a variable called STATIC_SPACE
with the site space repository name: severo/svelte-demo
:
Prepare the HF_TOKEN
secret
Create a new token in your settings: https://huggingface.co/settings/tokens:
- Fine-grained
- Token name:
space-build-svelte-demo
- Find the site space
svelte-demo
under Repositories permissions, and checkWrite access to contents/settings of selected repos
- Create token
Then copy the value and paste it in a new secret called HF_TOKEN
in the code space repository settings.
We now have one environment variable and one secret:
Commit and push
The last step is to commit your changes and push them to the code space on Hugging Face:
$ git add .
$ git commit -m "add Dockerfile and the app"
$ git push
Look at the building logs
Once pushed, the code space will be built automatically. You can see the logs at https://huggingface.co/spaces/severo/build-svelte-demo?logs=build
The last lines should show that the files have been uploaded to the site space, and then that the build fails, on purpose, as explained above:
--> RUN --mount=type=secret,id=HF_TOKEN,mode=0444,required=true huggingface-cli upload --token=$(cat /run/secrets/HF_TOKEN) --repo-type=space severo/svelte-demo /tmp/space . --delete "*"
Consider using `hf_********` for faster uploads. This solution comes with some limitations. See https://huggingface.co/docs/huggingface_hub/hf_******** for more details.
Removing 5 file(s) from commit that have not changed.
No files have been modified since last commit. Skipping to prevent empty commit.
https://huggingface.co/spaces/severo/svelte-demo/tree/main/.
DONE 1.1s
--> RUN exit 1
--> ERROR: process "/bin/sh -c exit 1" did not complete successfully: exit code: 1
Conclusion
The website is now deployed on the site space: https://huggingface.co/spaces/severo/svelte-demo
Let's recap the logic:
- create an empty static HTML space: the site space
- host your Svelte/React/Vue app in another Docker space: the code space
- add a Dockerfile to the code space with the commands to build and deploy to the site space
- any change pushed to the code space will upload the site space.
Side-effect: the code space can remain private while the site space is public.
Now it's your turn. Share your creations!