reactjstypescriptazureazure-web-app-servicenodejs-express-server

ReactVite Express NodeJs Azure app service. 404 The resource you are looking for has been removed, had its name changed, or is temporarily unavailable


Error: 404 Not Found The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

I have deployed a simple webapp to Azure app service. base url works fine but when I try to hit baseurl/hello endpoint I get the error.

Setup works locally, but I have been pondering my brains out to figure how I can get express to redirect requests in Azure.

web.config

<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <!-- SPA fallback rule to handle client-side routing -->
        <rule name="SPA Routing" stopProcessing="true">
          <match url="^(.*)$" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            <!-- Exclude /hello and any other endpoints -->
            <add input="{REQUEST_URI}" pattern="^/hello" negate="true" />
          </conditions>
          <action type="Rewrite" url="/index.html" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

package.json

  "scripts": {
    "dev": "nodemon -w src/server -x tsx src/server/main.ts",
    "start": "cross-env NODE_ENV=production tsx src/server/main.ts",
    "build": "vite build && cpx web.config dist/"

server/main.ts

import express from "express";
import ViteExpress from "vite-express";

const app = express();

app.get("/hello", (_, res) => {
  res.send("Hello Vite + React + TypeScript!");
});

const port = process.env.PORT || 3000;
ViteExpress.listen(app, +port, () =>
  console.log(`Server is listening on port ${port}...`)
);

project structure image. local, dist included

Azure App Service, wwwroot folder via Kudu


Solution

  • When I tried your code, even I was unable to redirect to /hello endpoint in Azure App Service.

    I added the below lines of code to the main.ts file. this setup serves all static files from the dist folder and allowing the front-end framework to handle the route.

    import path from 'path';
    
    
    app.use(express.static(path.join(__dirname, '../../dist')));
    
    app.get('*', (req, res) => {
      res.sendFile(path.join(__dirname, '../../dist', 'index.html'));
    });
    

    Below is the complete code of main.ts.

    src/server/main.ts:

    import express from 'express';
    import path from 'path';
    import { fileURLToPath } from 'url';
    import ViteExpress from 'vite-express';
    const app = express();
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
    app.use(express.static(path.join(__dirname, '../../dist')));
    app.get('/hello', (_, res) => {
      res.send('Hello Vite + React + TypeScript!');
    });
    app.get('*', (req, res) => {
      res.sendFile(path.join(__dirname, '../../dist', 'index.html'));
    });
    const port = process.env.PORT || 3000;
    ViteExpress.listen(app, +port, () =>
      console.log(`Server is listening on port ${port}...`)
    );
    

    In local environment vite-express handles both server and client, but in azure we need to define /hello route in vite.config.ts.

    vite.config.ts:

    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    export default defineConfig({
      plugins: [react()],
      server: {
        proxy: {
          '/hello': {
            target: `http://localhost:${process.env.PORT || 3000}`,
            changeOrigin: true,
          },
        },
      },
    });
    

    package.json:

    {
      "name": "my-vite-express-app",
      "private": true,
      "version": "0.0.0",
      "type": "module",
     "scripts": {
        "dev": "nodemon -w src/server -x tsx src/server/main.ts",
        "start": "cross-env NODE_ENV=production tsx src/server/main.ts",
        "build": "vite build && cpx web.config dist/"
     },
      "dependencies": {
        "cross-env": "^7.0.3",
        "express": "^4.21.1",
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
        "vite-express": "^0.19.0"
      },
      "devDependencies": {
        "@eslint/js": "^9.13.0",
        "@types/node": "^22.9.0",
        "@types/react": "^18.3.12",
        "@types/react-dom": "^18.3.1",
        "@vitejs/plugin-react": "^4.3.3",
        "cpx": "^1.5.0",
        "eslint": "^9.13.0",
        "eslint-plugin-react-hooks": "^5.0.0",
        "eslint-plugin-react-refresh": "^0.4.14",
        "globals": "^15.11.0",
        "tsx": "^4.19.2",
        "typescript": "~5.6.2",
        "typescript-eslint": "^8.11.0",
        "vite": "^5.4.10"
      }
    }
    

    tsconfig.json:

    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "lib": ["DOM", "ESNext"],
        "jsx": "react-jsx",
        "moduleResolution": "node",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "outDir": "dist",
        "types": ["node"]
      }
    }
    

    After changing main.ts, I am successfully redirected to the /hello endpoint.

    Azure Web App Output:

    enter image description here

    enter image description here