DEV Community

Cover image for Updating website content on schedule via GitHub Actions
Anton Prudkohliad
Anton Prudkohliad

Posted on • Originally published at prudkohliad.com

Updating website content on schedule via GitHub Actions

I would like to share my journey on building a self-sustainable content management system that does not require a content database in a traditional sense.

The Problem

The content (blog posts and bookmarks) of this website is stored in a Notion database:

The database with bookmarks –  Notion UI

The database with bookmarks –  Notion UI

The problem that I was trying to solve was to not have to deploy the website manually after each bookmark that I add there. And on top of that – keep the hosting as cheap as possible, because for me it does not really matter how fast the bookmarks that I add to my Notion database end up online.

So, after some research I came up with the following setup:

A high-level overview of the content delivery mechanism

A high-level overview of the content delivery mechanism

The system consists of several components:

  • The “Push to Main” action that deploys the changes
  • The “Update Content” action that downloads content from Notion API and commits the changes
  • The “Update Content on Schedule” action runs once in a while and triggers the “Update Content” action

Let us look into each one of them from the inside out in detail.

The “Push to Main” Workflow

There is not a lot to say here, pretty standard setup, – when there is a push to the main branch, this workflow builds the app and deploys it to Cloudflare Pages using the Wrangler CLI:

name: Push to Main
on:
  push:
    branches: [main]
  workflow_dispatch: {}
jobs:
  deploy-cloudflare-pages:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version-file: .node-version
          cache: pnpm
      - name: Install node modules
        run: |
          pnpm --version
          pnpm install --frozen-lockfile
      - name: Build the App
        run: |
          pnpm build
      - name: Publish Cloudflare Pages
        env:
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        run: |
          pnpm wrangler pages deploy ./out --project-name ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
Enter fullscreen mode Exit fullscreen mode

The “Update Content” Workflow

This Workflow can only be triggered “manually”… but also automatically because you can trigger it using a GitHub Personal Access Token, a.k.a. PAT. I initially wrote it because I wanted to deploy changes from my phone. It downloads the posts and bookmarks using the Notion API and then – if there are any change to the codebase – creates a commit and pushes it. In order to function properly, this workflow must be provided with a PAT that has “Read and Write access to code” of the repository:

name: Update Content
on:
  workflow_dispatch: {}
jobs:
  download-content:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # A Github Personal Access Token with access to the repository 
          # that has the follwing permissions:
          # ✅ Read and Write access to code
          token: ${{ secrets.GITHUB_PAT_CONTENT }}
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version-file: .node-version
          cache: pnpm
      - name: Install node modules
        run: |
          pnpm --version
          pnpm install --frozen-lockfile
      - name: Download articles content from Notion
        env:
          NOTION_KEY: "${{ secrets.NOTION_KEY }}"
          NOTION_ARTICLES_DATABASE_ID: "${{ secrets.NOTION_ARTICLES_DATABASE_ID }}"
        run: |
          pnpm download-articles
      - name: Download bookmarks content from Notion
        env:
          NOTION_KEY: ${{ secrets.NOTION_KEY }}
          NOTION_BOOKMARKS_DATABASE_ID: ${{ secrets.NOTION_BOOKMARKS_DATABASE_ID }}
        run: |
          pnpm download-bookmarks
      - name: Configure Git
        run: |
          git config --global user.email "${{ secrets.GIT_USER_EMAIL }}"
          git config --global user.name "${{ secrets.GIT_USER_NAME }}"
      - name: Check if anything changed
        id: check-changes
        run: |
          if [ -n "$(git status --porcelain)" ]; then
            echo "There are changes"
            echo "HAS_CHANGED=true" >> $GITHUB_OUTPUT
          else
            echo "There are no changes"
            echo "HAS_CHANGED=false" >> $GITHUB_OUTPUT
          fi
      - name: Commit changes
        if: steps.check-changes.outputs.HAS_CHANGED == 'true'
        run: |
          git add ./src/content
          git add ./public
          git commit -m "Automatic content update commit"
          git push
Enter fullscreen mode Exit fullscreen mode

The “Update Content on Schedule” Workflow

This one is pretty simple: it just runs every once in a while and triggers the workflow above. In order to function properly, this workflow must be provided with a GitHub PAT that has “Read and Write access to actions” of the repository. In my case it’s a different PAT:

name: Update Content on Schedule
on:
  schedule:
    - cron: "13 0,12 * * *"
  workflow_dispatch: {}
jobs:
  trigger-update-content:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Dispatch the Update Content workflow
        env:
          # A Github Personal Access Token with access to the repository 
          # that has the follwing permissions:
          # ✅ Read and Write access to actions
          GH_TOKEN: ${{ secrets.GITHUB_PAT_ACTIONS }}
        run: |
          gh workflow run "Update Content" --ref main
Enter fullscreen mode Exit fullscreen mode

Conclusion

For me this setup has proven to be really good and flexible. Because of the modular structure, the “Update Content” action can be triggered manually – e.g. from my phone while travelling. To me this was another valuable experience of progressive enhancement of a workflow.

Hope you find this helpful 😉

Top comments (0)