You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Netlify部署Node.js Express项目?解决404错误

问题描述

我是前端开发者,后端开发新手,用Node.js + Express搭建后端、React搭建前端开发了一款URL短链工具。本地运行完全正常,但部署到Netlify后出现404错误,无法正常使用。环境变量配置无误,但访问后端URL无任何响应,推测问题出在Netlify的构建配置上,求正确的构建配置方案及问题解决办法。

后端代码

const express = require('express');
const shortid = require('shortid');
const cors = require('cors');
const { resolve } = require('url');
const app = express();
const PORT = 5000;
require('dotenv').config();
const urlDatabase = {};

const corsOptions = {
   origin: '*',
   methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
   credentials: true,
   optionsSuccessStatus: 204,
};

app.use(cors(corsOptions));
app.use(express.json());

app.post('/shorten', (req, res) => {
  const { originalUrl } = req.body;
  const shortCode = shortid.generate();
  const shortUrl = `${process.env.BACKEND_URL}/${shortCode}`;

  urlDatabase[shortCode] = originalUrl;
  res.json({ shortUrl });
});

app.get('/:shortCode', (req, res) => {
  const { shortCode } = req.params;
  const originalUrl = urlDatabase[shortCode];

  if (originalUrl) {
     res.redirect(originalUrl);
  } else {
     res.status(404).json({ error: 'URL not found' });
  }
});

app.listen(PORT, () => {
  console.log(`Server is running on '${process.env.BACKEND_URL}:${PORT}'`);
});

前端代码

import React, { useState } from 'react';
import axios from 'axios';
import { Form, Button, Alert } from 'react-bootstrap';
import './App.css';

const App = () => {
  const [originalUrl, setOriginalUrl] = useState('');
  const [shortenedUrl, setShortenedUrl] = useState('');
  const [errorMessage, setErrorMessage] = useState('');

  const shortenUrl = async () => {
    try {
      if (!originalUrl) {
        setErrorMessage('Por favor, digite uma URL válida.');
        setShortenedUrl('');
      } else {
        console.log('Backend URL:', process.env.REACT_APP_BACKEND_URL);
        const response = await axios.post(`${process.env.REACT_APP_BACKEND_URL}/shorten`, {
          originalUrl,
        });
        
        setShortenedUrl(response.data.shortUrl);
        setErrorMessage('');
      }
    } catch (error) {
      console.error('Error shortening URL:', error);
      setErrorMessage('Ocorreu um erro ao encurtar a URL. Tente novamente.');
      setShortenedUrl('');
    }
  };

  return (
    <div className="App">
      <div className="container">
        <h1>Encurtador de Link</h1>
      </div>
      <Form className='form'>
        <Form.Group controlId="formOriginalUrl" className='form-url-original'>
          <Form.Label className='text-url'>Insira o Link Original</Form.Label>
          <Form.Control
            className='input'
            type="url"
            placeholder="Digite o Link original"
            value={originalUrl}
            onChange={(e) => setOriginalUrl(e.target.value)}
          />
        </Form.Group>

        <Button variant="primary" className='btn-url-original' onClick={shortenUrl}>
          Encurtar Link
        </Button>
      </Form>

      {errorMessage && <Alert variant="danger" className="mt-3 new-url">{errorMessage}</Alert>}

      {shortenedUrl && !errorMessage && (
        <div className="mt-3 new-url">
          <p>Seu Novo Link</p>
          <a href={shortenedUrl} target="_blank" rel="noopener noreferrer">
            {shortenedUrl}
          </a>
        </div>
      )}
    </div>
  );
};

export default App;

解决方案

核心问题

Netlify默认是静态站点托管平台,不会自动运行Express后端服务。本地环境中前后端分开启动,但Netlify需要将后端逻辑转为**Netlify Functions(无服务器函数)**部署,同时配置重写规则转发API请求。

步骤1:重构后端为Netlify Functions

Netlify Functions要求函数放在项目根目录的netlify/functions文件夹下,每个函数对应一个接口:

项目结构调整

你的项目/
├── netlify/
│   └── functions/
│       ├── shorten.js  # 处理URL缩短请求
│       └── redirect.js # 处理短链跳转
└── src/                # React前端代码

编写shorten.js函数

const shortid = require('shortid');
// 注意:Netlify Functions是无状态的,内存存储会在函数重启后丢失,建议后续替换为外部数据库(如Supabase/Redis)
const urlDatabase = {};

exports.handler = async (event, context) => {
  // 处理CORS预检请求
  if (event.httpMethod === 'OPTIONS') {
    return {
      statusCode: 204,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
    };
  }

  if (event.httpMethod === 'POST') {
    try {
      const { originalUrl } = JSON.parse(event.body);
      const shortCode = shortid.generate();
      // 使用Netlify站点域名作为短链前缀
      const shortUrl = `${process.env.URL}/${shortCode}`;
      
      urlDatabase[shortCode] = originalUrl;

      return {
        statusCode: 200,
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ shortUrl }),
      };
    } catch (err) {
      return {
        statusCode: 500,
        headers: { 'Access-Control-Allow-Origin': '*' },
        body: JSON.stringify({ error: '处理请求失败' }),
      };
    }
  }

  return {
    statusCode: 405,
    headers: { 'Access-Control-Allow-Origin': '*' },
    body: JSON.stringify({ error: '不支持的请求方法' }),
  };
};

编写redirect.js函数

const urlDatabase = {}; // 同样需要替换为外部数据库

exports.handler = async (event, context) => {
  const { shortCode } = event.queryStringParameters;
  const originalUrl = urlDatabase[shortCode];

  if (originalUrl) {
    return {
      statusCode: 302,
      headers: {
        Location: originalUrl,
        'Access-Control-Allow-Origin': '*',
      },
    };
  } else {
    return {
      statusCode: 404,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ error: 'URL不存在' }),
    };
  }
};

步骤2:配置Netlify重写规则

在项目根目录创建netlify.toml文件,将前端API请求转发到对应函数:

[build]
  command = "npm run build"
  publish = "build"

[[redirects]]
  from = "/shorten"
  to = "/.netlify/functions/shorten"
  status = 200

[[redirects]]
  from = "/:shortCode"
  to = "/.netlify/functions/redirect?shortCode=:shortCode"
  status = 302

步骤3:调整前端请求地址

前端无需再配置独立的后端URL,直接使用Netlify站点域名即可,可通过window.location.origin动态获取:

const response = await axios.post(`${window.location.origin}/shorten`, {
  originalUrl,
});

步骤4:解决无状态存储问题

Netlify Functions每次调用后内存会被清空,urlDatabase的数据会丢失,必须将短链数据存储到外部数据库,比如:

  • Supabase(免费额度满足小型项目需求)
  • Redis
  • FaunaDB

以Supabase为例,需安装@supabase/supabase-js,并在函数中替换内存存储逻辑为数据库的读写操作。

步骤5:部署到Netlify

  1. 将代码推送到GitHub/GitLab仓库
  2. 在Netlify后台关联该仓库,配置所需环境变量(如数据库密钥)
  3. 触发自动构建,Netlify会自动识别netlify.toml和Functions文件夹完成部署

本地测试

安装Netlify CLI:npm install -g netlify-cli,然后运行netlify dev即可在本地模拟Netlify环境,测试函数和前端的交互。


内容的提问来源于stack exchange,提问作者Renan Alexandre Berton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:13:11