Modern Web App - Start of the Journey
Tags: nextjs, programming, azure
Published: 2024-01-09 19:52:10

Recently, I wanted to test a few technologies and use this excuse to start a new project around [StarFinder](https://paizo.com/starfinder/) as a support for my discovery journey :smile:

This post and the next ones, are the documentation of this journey and, as such, are not a "tutorial" on starting a new project with nextjs, vitest or typescript but the sum of my findings and choices. See them as my "travel log"!

## The Project

[![StarFinder](https://paizo.com/image/content/Starfinder/logo.png)](https://paizo.com/starfinder/)

My goal is to implement a wizard to help in the character creation process for the tabletop role playing game [StarFinder](https://paizo.com/starfinder/). A few solutions already exists but I found them lacking on the UI/UX part and I wanted to have a solution that guide the user while having a more modern look.

My github repo: https://github.com/superfaz/starfinder

- **First goal: Creation** - A user should be able to create a level 1 character based on the official rule book extended with the character option book. As a result, he should have a proper character sheet (at least printed; potentially online) that he can use to play his character.

- **Second goal: Level-up** - A user should be able to apply its level-ups and be guided in doing so (automatic updates and help provided to know what is authorized to him or not).

- **Third goal: Sharing** - A user should be able to share its character sheets with other people and in particular Game Masters.

## My (initial) Stack

[![My Stack](https://skillicons.dev/icons?i=nextjs,ts,azure)](https://skillicons.dev)

I wanted to do this project with the following key technologies:
- **typescript** with **swc** for coding
- **nextjs** to test its Server-Side Rendering capabilities
- **copilot** in support as the project will be open-source
- **azure static web app** for deployment

> _As the project moved forward this stack has evolved and I will give more details about those changes on later posts._

## The First Step

My initial objective was to simply set-up the project, create the associated github space and prepare the release via github actions and azure static web app. And start to code my proof-of-concept.

During this process, I did a few interesting discoveries regarding node support on github and azure stacks:

1. Github Actions Runners is using Node 18 per default (and not the 20 which is the latest LTS at the moment).

    Source: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md

    The following step can be added to a github action workflow to install the desired version:
    ```yaml
      - name: Setup Node.js environment
        uses: actions/setup-node@v4
        with:
          node-version: "20.x"
    ```

2. SWA deployment, based on Oryx, is using node 16 per default. That's indeed a strange choice. Oryx will read the `package.json` file of the project to determine the version of node to use based on the `engine`/`node` property:

    ```json
    "engines": {
      "node": ">18.0.0"
    }
    ```

3. SWA runner doesn't support node 20, even if the deployment allows it. You have to add a restriction on the same `engines`/`node` property:

    ```json
    "engines": {
      "node": ">18.0.0 <20.0.0"
    }
    ```

    Sources: https://github.com/Azure/static-web-apps/issues/1349 and https://github.com/Azure/static-web-apps-cli/issues/756

## Optimizing the workflow with Cache

In order to keep the Github Actions workflow running smoothly, adding some cache for **yarn** and **nextjs** was a mandatory step.

For **nextjs**, the configuration is straightforward and based on the official documentation:

Source: https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching

```yaml
      - name: Cache for Next
        uses: actions/cache@v3
        with:
          path: ${{ github.workspace }}/.next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
```

For yarn, the official documentation extracts the path of the global cache before using `actions/cache`. I preferred to move the cache to a known location to simplify the configuration. Here is my  set-up:

```yaml
      - name: Setup Yarn
        run: |
          corepack enable
          yarn config set enableGlobalCache false
          yarn config set cacheFolder .yarn/cache

      - name: Cache for Yarn
        uses: actions/cache@v3
        with:
          path: ${{ github.workspace }}/.yarn/cache
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-
```

## The Proof of Concept

While developing the first elements of the app with nextjs, it appears quite quickly that I will not exploit the Server-Side Rendering a lot at the beginning. As using Client rendering is required for a lot of user interactions.

A small configuration was required for the nextjs build to successfully being deployed as a SWA. In the next.config.mjs, I added the following:

```typescript
  if (process.env.GITHUB_WORKFLOW) {
    // Configuration for Azure Static Web Apps
    console.log("Azure Static Web Apps configuration");
    return { ...standardConfig, output: "standalone" };
  } else {
    // Default configuration
    return standardConfig;
  }
```

The POC was able to help the user to select a Race, a Theme and a Class while providing meaningful data about those choices (and sub-forms when needed).

This implementation was based on json files for the data. And it was quite clear for me that one of the next steps will be to extract those into a NoSQL data source.

![Screenshot of the project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tz47ehztz4j3mk0f6kzo.png)
_current status_

## Next step: Adding tests

The next post will be about the deployment of **vitest** for the project and why it was finally required to do a switch to **jest**.