Przejdź do głównej treści.

Cloudflare Pages

Autor:  Mati
Opublikowano:

Dlaczego strona statyczna?

Gdy powstawał ten blog, to zastanawiałem się, w jaki sposób najlepiej go hostować. Zależało mi na czymś co jest stosunkowo bezobsługowe i jednoczesnie elastyczne. Raczej odrzucałem wszelkie platformy, które wymagają BackEndu, ze względu na koszty. Są na pewno wygodniejsze, nawet dla nie-technicznych osób. Dobry edytor WYSWIG potrafi czynić cuda! Niemniej mając nieco doświadczenia w programowaniu, zdecydowałem się na jakiś static site generator. Dzięki czemu mogłem rozważyć darmowe opcje hostowania. Także czynnik ekonomiczny, jak i wygoda przechowywania treści bezpośrednio w githubie pomogła mi podjąć taką decyzję.

Astro

Mając podjętą decyzję o tym, że chcę stronę statyczną, przyszedł wybór generatora. Początkowo chciałem użyć hugo. Po zainstalowanie wszystko wyglądało spoko, niemniej miałem duzy problem, żeby zmodyfikowac nieco szablon pod siebie. Jako, że nie jestem biegły w Go, to postanowiłem szukać dalej. Zacząłem się skupiac bardziej na narzędziach ze świata JS/TS, do których mi zdecydowanie bliżej. I tak trafiłem na astro. Wydał mi sie on o tyle ciekawy, ponieważ pozwala osadzać kod z różnych frameworków (kto wie może kiedyś mi się to przyda). Jak i ma możliwość Server Side Renderingu. Póki co nie korzystam z tych funkcji, ale miło wiedzieć, że mam taka opcję, gdyby była mi potrzebna. I w razie czego nie będę musiał przenosić wszystkiego w nowe miejsce. Również konfiguracja szablonu wydała mi się nieco prostsza niż w hugo.

Szablon

Nie wiem czy to standard, czy może ja coś przeoczyłem, niemniej astro i użyty szablon mocno zlewają się ze sobą. Nie za bardzo są gotowe szablony które można ściągnąć i sobie doinstalować. Astro jest dosyć elastyczne i pozwala na dużą dowolność w konwencjach użytych w szablonach, co przekłada się jednak na brak jakiegoś ogólnego standardu. Jest kilka ważnych miejsc (o których się dowiedziałem tworząc tego bloga), które będą wszędzie takie same. Z tego względu, podczas pobierania szablonu z neta, pobiera się praktycznie cały setup projektu. Co dla mnie było troche zaskakujące, bo nie mogłem sobie do swojego repo “dograć” nowy template i tylko go włączyć

To co takiego jest stałe w astro?

Między innymi struktura folderu pages odwzorowuje routing w projekcie, więc zawiera szablony astro, które są renderowane na poszczególnych podstronach. Drugim ważnym elementem jest plik content.config.ts, który definiuje źródła dla naszych danych do projektu.

Cała reszta działa na importach. Strony astro potrafią już importowac odpowiednie komponenty i elementy niezbędne w danym widoku.

Github

Workflow podzieliłem sobie na 2 branche dev, na którym pracuję, oraz master z którego budowana jest strona. W tym momencie mam dodany GitHub Actions, który automatycznie podbija mi branch master w momencie gdy pushuję nowe zmiany na dev. Nie chciałem używać bezpośrednie brancha master z tego względu, że może kiedyś pojawi się potrzeba dodania jakiegoś kroku pośredniego. Być może pojawią się jakieś testy. I dopiero jak one będą pozytywne to branch master zostanie zaktualizowany. Natomiast integracja cloudflare pozwala budować każdą nowa zmianę na masterze. Dzięki czemu, nie jest to w żaden sposób problemem teraz, bo dzieje się automatycznie. Natomiast w przyszłości, mam możliwość rozdzielenia deployu i publikowania i uzupełnienia procesu o dodatkowe kroki. Może nigdy z tego nie skorzystam 🤷🏻‍♂️. Dodałem też akcje z webhookiem dla Discorda, aby dostać powiadomienie, gdyby jednak ustawiony proces miał jakiś problem.

Cloudflare Pages

Do hostingu użyłem cloudflare, ma większe możliwości niż github pages. Początkowo myślałem, żeby użyć gotowego szablonu z cloudflare dla astro, jednak zawierał on zbyt wiele zależności, których na ten moment nie potrzebuję. Była w nim konfiguracja pod server side rendering, która nie jest mi do niczego potrzebna. Aby zacząć hostować projekt w cloudflare pages najpierw musiałem go dodać w zakładce “Compute (workers)” -> “Workers & Pages”. Następnie przełączyłem sie na zakładkę pages. Tutaj początkowo popełniłem błąd używając szablonu dla astro, który dla moich potrzeb był zbędny, ponieważ konfigurował server side rendering. I dodałem istniejący projekt z githuba. W ustawieniach tak stworzonego projektu, dodałem zmianę, aby budowac jedynie branch master, ponieważ domyślnie cloudflare buduje wszystkie branche.

Redirects

Początkowo nie zwróciłem uwagi, ale cloudflare pozwala na stworzenie specjalnych plików, które potrafią ustawiać przekierowania 301 oraz 302. Okazało się, że owe przekierowania stworzone poprzez taki plik konfiguracyjny, który był tworzony przez astro, obsługiwany jest przez serwer. Także jest on ograniczony do konkretnej liczby użyć. Okazało się, że nie usunąłem adaptera po moich początkowych testach, który dodawał mi ten plik. Po wywaleniu tej libki, wszystko zaczęło śmigać.

pages.dev

To jest kolejny element, który generuje cloudflare, a który nieco mi przeszkadzał. Mianowicie, cloudflare hostuje każdy taki projekt pod własną domeną z końcówką pages.dev. I chciałem uniknąć sytuacji, gdy moja strona jest dostępną pod 2 adresami jednocześnie. W tym celu wystarczyło dodać “bulk redirect”. Okazuje się, że nie muszę mieć dostępu do zarządzania domeną pages.dev, aby mimo wszystko móc z niej przekierować ruch do własnej strony. Wystarczy ustawić przekierowanie z https://pages.dev/ na https://devnotes.pl i wszystko zaczyna działać jak należy. Możesz spróbować wejść na stronę blog-8wa.pages.dev i zobaczysz, że nastąpi przekierowanie na stronę główną tego bloga.

GitHub Action

A poniżej cała GitHub Action, która została stworzona z pomocą AI. Wiedziałem co chcę, ale byłem nieco zbyt leniwy, aby wszystko samemu wstukiwać na klawiaturze. No i AI zasugerowało kilka dodatkowych kroków i sprawdzeń o których początkowo nie pomyślałem. 😅

name: Promote to master

on:
  push:
    branches:
      - dev
  workflow_dispatch:

permissions:
  contents: write
  actions: read

jobs:
  sync-branches:
    name: Sync master with dev
    runs-on: ubuntu-latest

    if: github.event_name == 'push' && github.ref == 'refs/heads/dev'

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Verify branches exist
        run: |
          echo "Checking if required branches exist..."
          if ! git branch -r | grep -q "origin/dev"; then
            echo "Error: dev branch not found"
            exit 1
          fi
          if ! git branch -r | grep -q "origin/master"; then
            echo "Error: master branch not found"
            exit 1
          fi
          echo "Both branches verified successfully"

      - name: Configure Git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Switch to master branch
        run: |
          echo "Switching to master branch..."
          git checkout master
          git pull origin master

      - name: Check if update is needed
        id: check_diff
        run: |
          echo "Checking differences between master and dev..."
          if git diff --quiet master origin/dev; then
            echo "No differences found - master is already up to date with dev"
            echo "needs_update=false" >> $GITHUB_OUTPUT
          else
            echo "Differences found - update needed"
            echo "needs_update=true" >> $GITHUB_OUTPUT
            
            echo "Changes to be applied:"
            git log --oneline master..origin/dev | head -10
          fi

      - name: Analyze branch relationship
        if: steps.check_diff.outputs.needs_update == 'true'
        run: |
          echo "Analyzing branch relationship before merge..."

          MASTER_AHEAD=$(git rev-list --count origin/dev..master)
          DEV_AHEAD=$(git rev-list --count master..origin/dev)

          echo "Master is ahead by: $MASTER_AHEAD commits"
          echo "Dev is ahead by: $DEV_AHEAD commits"

          if [ "$MASTER_AHEAD" -gt 0 ]; then
            echo "❌ ERROR: Master has commits not present in dev!"
            echo "This is unsafe - manual intervention required."
            echo ""
            echo "Commits unique to master:"
            git log --oneline origin/dev..master
            echo ""
            echo "Please manually merge or rebase these changes into dev first."
            exit 1
          fi

          echo "✅ Safe to proceed - master has no unique commits"

      - name: Update master to dev level
        if: steps.check_diff.outputs.needs_update == 'true'
        run: |
          echo "Updating master branch to match dev..."

          if git merge --ff-only origin/dev; then
            echo "✅ Fast-forward merge successful"
          else
            echo "❌ Fast-forward merge failed despite pre-checks!"
            echo "This should not happen - please check repository state"
            echo "Recent commits on both branches:"
            git log --oneline --graph master origin/dev --max-count=5
            exit 1
          fi

      - name: Push updated master
        if: steps.check_diff.outputs.needs_update == 'true'
        run: |
          echo "Pushing updated master branch..."
          git push origin master
          echo "Master branch successfully updated to match dev"

      - name: Verify synchronization
        if: steps.check_diff.outputs.needs_update == 'true'
        run: |
          echo "Verifying synchronization..."
          git fetch origin

          if git diff --quiet origin/master origin/dev; then
            echo "✅ Success: master and dev branches are now synchronized"
            
            echo "Latest commits on master:"
            git log --oneline -5 origin/master
          else
            echo "❌ Error: Synchronization failed - branches still differ"
            exit 1
          fi

      - name: Handle failure
        if: failure() && steps.check_diff.outputs.needs_update == 'true'
        run: |
          echo "❌ Workflow failed during synchronization"
          echo "Master branch was not modified - no rollback needed"
          echo "Check the logs above for specific error details"
          echo "Manual intervention may be required to resolve branch conflicts"

  notify:
    name: Notify about sync
    runs-on: ubuntu-latest
    needs: sync-branches
    environment: DISCORD
    if: always() && needs.sync-branches.result != 'success'

    steps:
      - name: Prepare notification message
        id: message
        run: |
          RESULT="${{ needs.sync-branches.result }}"

          case "$RESULT" in
            "success")
              echo "emoji=✅" >> $GITHUB_OUTPUT
              echo "status=Success" >> $GITHUB_OUTPUT
              echo "message=Master branch successfully synchronized with dev" >> $GITHUB_OUTPUT
              ;;
            "skipped")
              echo "emoji=ℹ️" >> $GITHUB_OUTPUT
              echo "status=Skipped" >> $GITHUB_OUTPUT
              echo "message=No synchronization needed - master already up to date" >> $GITHUB_OUTPUT
              ;;
            "cancelled")
              echo "emoji=⏹️" >> $GITHUB_OUTPUT
              echo "status=Cancelled" >> $GITHUB_OUTPUT
              echo "message=Workflow was cancelled during execution" >> $GITHUB_OUTPUT
              ;;
            *)
              echo "emoji=❌" >> $GITHUB_OUTPUT
              echo "status=Failed" >> $GITHUB_OUTPUT
              echo "message=Synchronization failed - check workflow logs" >> $GITHUB_OUTPUT
              ;;
          esac

      - name: Send Discord notification
        uses: Ilshidur/action-discord@master
        env:
          DISCORD_WEBHOOK: ${{ secrets.WEBHOOK_URL }}
        with:
          args: |
            **${{ steps.message.outputs.emoji }} GitHub Action: ${{ github.workflow }}**

            **Repository:** `${{ github.repository }}`
            **Branch:** `${{ github.ref_name }}`
            **Status:** ${{ steps.message.outputs.status }}

            ${{ steps.message.outputs.message }}

            [View Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})