Solution 1:

Azure needs a web.config file and also a server.js/index.js as a starting point else it won't be able to start.

I recommend changing your folder structure. See the example below https://github.com/zeit/next.js/tree/master/examples/custom-server

Create the server.js file and copy the information from the above mentioned github repo. In the package.json file replace the dev build and start to

"dev": "node server.js",
"build": "next build",
"start": "node server.js"

Now you can just use node server.js to run your code.

While uploading it to azure, in your root directory create a file call web.config and add the code below.

<?xml version="1.0" encoding="utf-8"?>
<!--
     This configuration file is required if iisnode is used to run node processes behind
     IIS or IIS Express.  For more information, visit:
     https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
-->

<configuration>
  <system.webServer>
    <!-- Visit http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx for more information on WebSocket support -->
    <webSocket enabled="false" />
    <handlers>
      <!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
      <add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
    </handlers>
    <rewrite>
      <rules>
        <!-- Do not interfere with requests for node-inspector debugging -->
        <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
          <match url="^server.js\/debug[\/]?" />
        </rule>

        <!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
        <rule name="StaticContent">
          <action type="Rewrite" url="public{REQUEST_URI}"/>
        </rule>

        <!-- All other URLs are mapped to the node.js site entry point -->
        <rule name="DynamicContent">
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
          </conditions>
          <action type="Rewrite" url="server.js"/>
        </rule>
      </rules>
    </rewrite>

    <!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
    <security>
      <requestFiltering>
        <hiddenSegments>
          <remove segment="bin"/>
        </hiddenSegments>
      </requestFiltering>
    </security>

    <!-- Make sure error responses are left untouched -->
    <httpErrors existingResponse="PassThrough" />

    <!--
      You can control how Node is hosted within IIS using the following options:
        * watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
        * node_env: will be propagated to node as NODE_ENV environment variable
        * debuggingEnabled - controls whether the built-in debugger is enabled
      See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options
    -->
    <!--<iisnode watchedFiles="web.config;*.js"/>-->
  </system.webServer>
</configuration> 

After you have added and made changes to your routes depending on how you need by tweaking the server.js file.

Push it to azure, and your app will start running as azure will now run node server.js and will know where to find it. And also the web.config file will rewrite the URL so you don't have to add yoururl.azure.net:3000 you can just simply type in the URL and it will work.

Solution 2:

As on today(30-Oct-2021), you do not need a web.config file in Azure Linux webapp, which doesn't run on IIS. All you need is a command to start the web app from code on Linux web app with Node JS environment.

For your application, I would suggest do not custom port until unless it is required. In your package.json remove the port specification with next start command, that should solve the issue with the following suggested approach.

{
  "name": "frontend",
  ...
  "scripts": {
    ...
    "start": "next start",
    ...
  },
  ...
}

Azure Linux web app service (container) uses by default port 8080 and maps it to external http/https (80/443) port.

As per Microsoft documentation for deploying Node JS / Next JS application on Azure Linux web app, the recommended way is to use PM2 rather than using npm start (or) node server.js.

It is far easier to use PM2 than writing your own server.js, as PM2 is already available by default with NodeJS runtimes in azure web apps. Also PM2 provides a full-service app management platform.

To use it, just add ecosystem.config.js file to your Next JS app with following code.

module.exports = {
  apps: [
    {
      name: "my-nextJs-site",
      script: "./node_modules/next/dist/bin/next",
      args: "start -p " + (process.env.PORT || 3000),
      watch: false,
      autorestart: true,
    },
  ],
};

As per Microsoft documentation mentioned above, the deployed code will automatically start with PM2 as soon as it finds ecosystem.config.js file after deployment.

The container automatically starts your app with PM2 when one of the common Node.js files is found in your project:
bin/www
server.js
app.js
index.js
hostingstart.js
(or) One of the following PM2 files: process.json or ecosystem.config.js

Note: In case the the app doesn't start automatically even after this, you can start it manually with the following PM2 command as a startup command in azure webapp settings.

pm2 --no-daemon start /home/site/wwwroot/ecosystem.config.js

Solution 3:

I was able to get Next.js to run on Azure Appservices by making the following changes to my app. Make the following changes to your Express app and your package.json file.

// server.js

const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

// Your app will get the Azure port from the process.enc.PORT
const port = process.env.PORT || 3000;

app
  .prepare()
  .then(() => {
     const server = express()

     server.get('*', (req, res) => {
         return handle(req, res)
     })

     server.listen(port, err => {
        if (err) throw err
            console.log('> Ready on http://localhost:3000')
        })
     })
     .catch(ex => {
         console.error(ex.stack)
         process.exit(1)
     })

In you package.json file, you will need to make sure that you have scripts for postinstall and start. In the start you can add a port variable like the following.

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start -p $PORT",
  "postinstall": "next build"
}

I have a blog post on how to fix at Running Next.js on Azure App Services