Static Site Generation (SSG) is very similar to server side rendering except the entire site is pre-rendered up-front instead of on a request by request basis.
Many of the terms and concepts used in server-side rendering also apply to static site generation - so please read about server-side rendering before continuing.
Unlike most other static site generators that use a series of file structure conventions and command line tools to generate the site, with CodeOnly you develop your site just like a regular CodeOnly single page app.
You create components, pages and setup a router to handle navigation between those pages. You then run, test and debug the site just like any other single page app.
Once your site is ready for deployment, the generateStatic
function
can be used to render the entire site. Alternatively, you can
use CodeOnly's static site generator plugin for Vite to render the site
automatically as part of the build process.
generateStatic Function
Static site generation is implemented by the function generateStatic
.
import { generateStatic } from "@codeonlyjs/core";
// Generate static site
await generateStatic({
// Options here
});
For a list of available options, see generateStatic.
CodeOnly includes a Vite plugin that can be used to automatically render the site
after the main index.html
and scripts are bundled.
The following is an example vite.config.js
file that shows how it's used (in
fact this is the Vite config we use to build this site).
viteStaticCopy
is used to copy other static
resources to the output folder.viteGenerateStatic
are the same as above.See viteGenerateStatic
for more.
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import { viteGenerateStatic } from "@codeonlyjs/core";
// Vite config
export default defineConfig({
base: "/",
publicDir: false,
build: {
emptyOutDir: true,
outDir: './dist',
},
plugins: [
viteStaticCopy({
targets: [
{ src: 'public/*', dest: './public/' },
{ src: 'content/*', dest: './content/' },
],
}),
viteGenerateStatic({
prebuild: "./prebuild.js",
entryFile: "./main-ssr.js",
entryMain: "main",
entryHtml: "./dist/index.html",
entryUrls: [ "/" ],
pretty: true,
}),
]
})
When using static site generation there's two ways the final application can be delivered:
To understand these two approaches, let's consider a simple statically generated blog site where the blog posts are written as markdown files with "front-matter" describing meta data about the page. This is a fairly common approach with SSG.
In order to build the static site, we need to enumerate all the files, extracting the front matter which we'll use to render the various parts of the site.
Once the site is generated this however, there's the question of whether the scripts are still needed client side? ie: Are the scripts used for interactivity, or are they purely being used as a way to generate the HTML content?
This is where there are two main choices:
One approach is to just deploy the generated HTML as a fully static site where navigation between pages is normal browser page request navigation.
A second approach is for the site to remain a single page app with a
client side router. and navigation makes fetch
requests for the markdown
of any pages visited and updates the DOM directly.
There are pros and cons to both:
To deliver as a single page app, there's nothing further to do - the approach described above works this way as is.
To deliver as straight HTML without the CodeOnly scripts all that's required
is to remove the startup code from the index.html
file that's used as the
entryHtml
file.
For example, create an index-dev.html
that used during development and
runs the site as a single page app:
index-dev.html
<html>
<body>
<!-- This is where the Main component will be mounted -->
<div id="main"></div>
<script type="module">
// Call "main" method to mount the component
import { main } from "/main.js";
main();
</script>
</body>
</html>
When generating the site, use a copy of the file with the startup script removed:
index.html
<html>
<body>
<!-- This is where the Main component will be mounted -->
<div id="main"></div>
</body>
</html>
This works because with SSG, the entry point script file and function
name is passed to the generateStatic
function directly and the
index.html
is just a template into which the rendered content is
injected.
There are may ways to deploy a statically generated site and any simple file server should be able to handle the requirements, but might require some minimal configuration:
index.html
as the default file
for a directory..html
to URL requests
that don't include and extension.For example, if you're using nginx, you'll need a configuration that includes a section like this:
location / {
index index.html;
try_files $uri $uri/ $uri.html =404;
}
A popular and easy way to deploy a static site is with GitHub Pages, using GitHub Actions to publish the site.
To help get you started, here's a publish action that builds the site and publishes it to GitHub pages each time a push is made.
.github/workflows/publish.yml
name: Publish Site
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build
- name: Upload static files as artifact
id: deployment
uses: actions/upload-pages-artifact@v3
with:
path: dist/
# Deployment job
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4