Deploy React web application with Next.js and .NET API on Linux Host
Tags: javascript, react, nextjs, dotnet
Published: 2023-05-29 16:19:29

React.js is a great front-end frameworks and I have been combining it a lot with .NET API written in C#. 

It is one of my favourite duos.  

So far we have used react to build internal application such as dashboards, admin panels etc. for which things like SEO don't matter at all. 

However for anything publicly available, SEO is important specially if you are building a SaaS and you want it to be findable by Google and others.

I started working on a small project which required server side rendering and so Next.js had to step in.

The challenge was to set it all up with [Azure Pipelines](https://azure.microsoft.com/en-us/products/devops/pipelines) (aka Github Actions aka Team City) and deploy it on a Linux hosted with [Droplets](https://www.digitalocean.com/products/droplets). 

I got very confused about the process of building and deployment because whenever I reached out to the Next.js community I was told to run `next build` on my production server which I found a bit crazy. 

Additionally nobody seem to know the answer: https://stackoverflow.com/questions/76121837/how-can-i-run-next-js-with-self-hosted-node-js

Why I found it crazy? Well it is a first time I was told to build the project on the production server. **This is not how it is supposed to be done**. The way it is supposed to be done is that you commit the code to your repo, pipelines pulls it and builds the whole thing for you and all it has to be done is the transfer of the production build to the production server. Why on Earth I would build things on the production server? 

After some research I discovered something called standalone output which also had its issues:
https://stackoverflow.com/questions/76294578/cannot-find-module-next-server-font-manifest-json 

Finally I managed to figure this out and so I'm sharing it here for those who are lost as I was once: 

1. Set output to **standalone**

```js
const nextConfig = {
  reactStrictMode: true,
  output: 'standalone'
}

module.exports = nextConfig

```

2. Here is the pipeline **build **setup. I will use a sudo code to give an idea of what needs to be done. I add the complete file at the end of the this post.

```bash
# SM.Web is my root of the web project

yarn install

# overwrite your .env with environment specific stuff

echo "NEXT_PUBLIC_API_BASE_URL=https://api.yourawesomeapp.com" >> src/SM.Web/.env

yarn build

cp -R src/SM.Web/public "src/SM.Web/.next/standalone/public"

cp -R src/SM.Web/.next/static" src/SM.Web/.next/standalone/.next/static"

zip src/SM.Web/.next/standalone SM.Web.zip

# publish SM.Web.zip s artefact 
```

Here is **deployment **pipeline:

1. Download the [artefact ](https://www.jetbrains.com/help/teamcity/build-artifact.html) on to your Linux box using SSH key connection. I won't go here into a details. 
2. Replace current application with the artefact content

```bash
rm  -rf /var/apps/sm/staging-app/*

unzip -o /var/apps/sm/staging-app-build/SM.Web.zip -d /var/apps/sm/staging-app 

rm  /var/apps/sm/staging-app-build/SM.Web.zip

# for this command to work you need to setup `sm-staging` first
# go to /var/apps/sm/staging-web/
# and run: pm2 start "node server.js" --name "sm-staging"

pm2 reload sm-staging

```

Here is the complete `azure-pipelines.yaml`:

```yaml
trigger:
  - main

pool:
  vmImage: ubuntu-latest

variables:
  buildConfiguration: "Release"

steps:
  - task: CmdLine@2
    displayName: "[app] yarn install staging"
    inputs:
      script: "yarn install"
      workingDirectory: "src/SM.Web/"

  - task: Bash@3
    displayName: "[app] add build version"
    inputs:
      targetType: "inline"
      script: |
        touch src/SM.Web/.env
        echo "NEXT_PUBLIC_API_BASE_URL=https://api.yourawesomeapp.com" >> src/SM.Web/.env
        cat src/SM.Web/.env

  - task: CmdLine@2
    displayName: "[app-staging] yarn build"
    inputs:
      script: "yarn build"
      workingDirectory: "src/SM.Web/"

  - task: CopyFiles@2
    displayName: "[app-staging] copy public"
    inputs:
      SourceFolder: "src/SM.Web/public" 
      Contents: '**' 
      TargetFolder: "src/SM.Web/.next/standalone/public"

  - task: CopyFiles@2
    displayName: "[app-staging] copy static"
    inputs:
      SourceFolder: "src/SM.Web/.next/static" 
      Contents: '**' 
      TargetFolder: "src/SM.Web/.next/standalone/.next/static"

  - task: ArchiveFiles@2
    displayName: "[app-staging] create artifact"
    inputs:
      rootFolderOrFile: "src/SM.Web/.next/standalone"
      includeRootFolder: false
      archiveType: "zip"
      archiveFile: "$(Build.ArtifactStagingDirectory)/sm-web/SM.Web.zip"

  - task: UseDotNet@2
    displayName: "[api] use dotnet 7"
    inputs:
      packageType: "sdk"
      version: "7.0.x"
      performMultiLevelLookup: true

  - task: DotNetCoreCLI@2
    displayName: "[api] restore"
    inputs:
      command: "restore"
      projects: "src/SM.Api/SM.Api.csproj"
      feedsToUse: "select"

  - task: DotNetCoreCLI@2
    displayName: "[api] build"
    inputs:
      command: "build"
      projects: "src/SM.Api/SM.Api.csproj"
      arguments: "--configuration $(BuildConfiguration)"

  - task: DotNetCoreCLI@2
    displayName: "[api] publish"
    inputs:
      command: "publish"
      publishWebProjects: true
      arguments: "-c $(buildConfiguration) -o $(Build.ArtifactStagingDirectory)/sm-api"

  - task: PublishPipelineArtifact@1
    displayName: "publish artifact"
    inputs:
      targetPath: "$(Build.ArtifactStagingDirectory)"
      artifactName: "sm"

```

What does your Linux box need?
- [Nodejs](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-20-04)
- [PM2](https://pm2.keymetrics.io/)

:rocket: Good luck! :rocket: