I am creating a custom built static site to replace an old WordPress site for a client. The main aim is to make it easier to maintain and faster to load. Choosing Cloudflare pages is my default for static sites these days, and I wanted to be able to deploy the website with a simple git push. For a few other websites I have built this can all be done in the Cloudflare dashboard, but for this particular site the requirement was to build a series of HTML pages with a python script and jinja2 template, with the data coming from CSV files. This can obviously be done locally and then pushed to Github / Cloudflare, but I wanted to do this in one step, by running the python script on Github Actions. Many of the static site generators have their own pre-built actions, but I needed to create a custom one. This is also useful knowledge as it could be used to run any kind of build action post push to github, whether that is using python to generate HTML or something else.

Setup Github repo

The github repo is structured as follows, and all of the html files generated will get but in the ‘output’ folder (known as the build folder in Cloudflare pages.

├── custom-build-script.py
├── output
│   └── custom.css
├── README.md
├── requirements.txt
├── source
│   ├── menu_main.csv
│   └── blog_pages.csv
└── templates
    ├── base.html
    ├── menu.html
    └── blog.html

Create Cloudflare Pages Deployment

To setup the Cloudflare Pages Deployment, you can follow the instructions here (https://developers.cloudflare.com/pages/get-started/), which simply involves connecting your github account to Cloudflare and choosing the correct repo, branch and folder. You don’t need to specify any build commands to Cloudflare, as we’re going to use github actions for that part.

Add Custom Action to Github repo

In the github repo you need to add this file: .github/workflows/customaction.yml
(https://github.com/marketplace/actions/cloudflare-pages-github-action)

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      deployments: write
    steps:

      - name: checkout repo content
        uses: actions/checkout@v3 # checkout repo to the runner

      - name: setup python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10.6'
          
      - name: install python packages
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          
      - name: execute py script
        run: python custom-build-script.py
          
      - name: commit files
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add -A
          git commit -m "github action auto build" -a
          
      - name: push changes
        uses: ad-m/github-push-action@v0.6.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: main  

      - name: Publish to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CF_API_KEY}}
          accountId: ${{ secrets.CF_ACCOUNT_ID}}
          projectName: NAME_OF_PROJECT
          branch: main
          directory: output

 

Steps
Each of the steps are performed in a Github runner (docker on linux), and many have pre-set actions you can simply reference, (see ‘uses’) such as repo checkout, python setup and publish to Cloudflare. The other custom actions can be ‘run’ as by specifying command line options. Here that includes installing the python env, running the python script and committing the changes back to the repo. The key step is running the custom-build-script.py which builds the html. Check this post out for an interesting critique of Github Actions.

Build HTML with Python and Jinja2
Here is a simplified example of custom-build-script.py, along with the jinja2 templates and a CSV file containing the data.

custom-build-script.py:

import pandas as pd
from jinja2 import Environment, FileSystemLoader
import time

file_loader = FileSystemLoader('templates')
env = Environment(loader=file_loader)

def create_pages(data_source, template_file, folder):
    template = env.get_template(template_file)
    pages = pd.read_csv(data_source, encoding='utf-8')
    for index, page in pages.iterrows():
        output = template.render(data=page)
        output_filepath = 'output/' + folder + \
            '/' + str(index + 1).zfill(2) + '.html'
        with open(output_filepath, "w") as fh:
            fh.write(output)

create_pages('source/blog_pages.csv', 'blog.html', 'blog') # create blog

blog_pages.csv:

id,title,content,imgurl
1,Next Level Python,"If you want to level up your python, here are ten top things to learn",python.jpg
2,Code Editor Review,"Using a code editor you are comfortable with can dramatically improve your proficiency",eds.jpg
3,Top 5 AWS services to learn,"Here are the top 5 basic services which you should know on AWS",aws.jpg
4,Raspberry Pi Project List,"With Raspberry Pi, only your imagination is the limit",pi.jpg

blog.html: (note base.html not shown but contains opening html tags, header, css and meta data)

{% extends "base.html" %}
{% block content %}
<div class="jumbotron text-center">
    <div class="h3"">{{data.title}}</div>
</div>
<div>
    <img src="{{data.imgurl}}">
</div>
<div class="main-content">
   {{data.content}}
</div>
{% endblock %}

So we’ve got a minimal example of creating a static website on Cloudflare pages using python and github actions to run the build. You could take this in other directions too, such as running the build on a schedule based on taking some external data and automatically building and commiting the changes.