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

Next.js晚于Flask API启动时无法渲染API数据的问题求助

问题修复:Next.js先启动后Flask API未就绪导致数据渲染异常

问题描述

我正在开发一个以Flask API为后端的Next.js应用,Main组件负责获取并展示Flask API返回的数据。但遇到启动顺序相关问题:若先启动Next.js应用,再部署Flask API,Next.js会将存储API返回数据的对象渲染为null,导致无数据返回并触发报错;但后续刷新页面,或先启动Flask API再启动Next.js时,功能则正常。推测原因是Flask API在Next.js启动后部署时未完全就绪,但不知如何修复。当前使用AWS的S3存储数据,DynamoDB作为数据库。尝试过延迟加载、防抖函数(代码仍保留,用于减少API请求次数)等方法,但问题仍未解决。

核心原因

Next.js启动后会立即发起API请求,此时Flask服务尚未就绪,请求失败后返回null,而现有代码没有处理请求失败后的自动重试逻辑,导致组件一直持有null值的menuData,直到手动刷新页面重新发起请求。

修复方案

1. 给API请求添加重试机制

在前端API请求函数中封装重试逻辑,当请求失败(如连接超时、服务未响应)时,自动重试多次,直到Flask服务就绪。

2. 后端返回正确的状态码

修改Flask API,当服务未初始化完成或无数据时,返回503 Service Unavailable状态码,而非200,让前端能准确判断是否需要重试。

3. 组件内添加自动重试逻辑

在使用menuData的父组件中,跟踪请求状态,当数据未获取到时,定时自动重新发起请求。

代码修改示例

前端API请求函数优化(添加重试)

import axios from 'axios'

const API_BASE_URL = 'http://127.0.0.1:5000'
const MAX_RETRIES = 5;
const RETRY_DELAY = 2000; // 每次重试间隔2秒

// 封装重试逻辑
const withRetry = async <T>(fn: () => Promise<T>): Promise<T | null> => {
  let retries = 0;
  while (retries < MAX_RETRIES) {
    try {
      return await fn();
    } catch (error) {
      retries++;
      console.log(`请求失败,重试第${retries}次...`);
      await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
    }
  }
  console.error('重试次数耗尽,请求失败');
  return null;
};

export const fetchDiningHalls = async () => {
  return withRetry(async () => {
    const response = await axios.get(`${API_BASE_URL}/return_location_halls`)
    return response.data.dining_halls
  }) || [];
}

export const fetchDatesForDiningHall = async (diningHall: string) => {
  return withRetry(async () => {
    const response = await axios.get(`${API_BASE_URL}/return_dates/${diningHall}`);
    return response.data.dates
  }) || [];
}

// 修正原函数中的语法错误
export const fetchMenuDataForJ2Dining = async () => {
  try {
    const response = await axios.get(`${API_BASE_URL}/return_meals/J2 Dining/Wednesday, June 31`);
    return response.data;
  } catch (error) {
    console.error(`Error fetching menu data for J2 Dining on Wednesday, June 31:`, error);
    return null;
  }
};

export const fetchMenuData = async (diningHall: string, date: string) => {
  return withRetry(async () => {
    const response = await axios.get(`${API_BASE_URL}/return_meals/${diningHall}/${date}`, {
      params: { hall: diningHall, date: date }
    });
    return response.data;
  });
};

Flask后端状态码修正

from flask import Flask, jsonify
from flask_cors import CORS
import boto3
import os
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
cors = CORS(app)

TABLE_NAME = os.environ['TABLE_NAME']

db = boto3.resource('dynamodb')
table = db.Table(TABLE_NAME)

def get_latest_data():
  try:
    response = table.scan()
    items = response.get('Items', [])

    if not items:
      print('No items available!')
      return None
    
    items_sorted = sorted(items, key=lambda x: x['timestamp'], reverse=True)
    latest_data = list(items_sorted[0]['data'].values())[0]
    return latest_data
  except Exception as e:
    print(str(e))
    return None
  
@app.route('/')
def home():
  return jsonify({'message': 'Hello World!'})

@app.route('/return_dates/<string:dining_hall>', methods=['GET'])
def return_dates(dining_hall):
  data = get_latest_data()
  if data is None:
    # 服务未就绪,返回503状态码
    return jsonify({'error': 'No data available'}), 503
  
  dates = data[dining_hall]['dates']
  return jsonify({'dates': dates})

@app.route('/return_location_halls', methods=['GET'])
def return_location_halls():
  data = get_latest_data()
  if data is None:
    return jsonify({'error': 'No data available'}), 503

  dining_halls = list(data.keys())
  return jsonify({'dining_halls': dining_halls})

@app.route('/return_meals/<string:dining_hall>/<string:date>', methods=['GET'])
def return_meals_dictionary(dining_hall, date):
  data = get_latest_data()
  if data is None:
    return jsonify({'error': 'No data available'}), 503
  
  menu_items = data[dining_hall]['menus']
  for index, item in enumerate(menu_items):
    if list(item.keys())[0] == date:
      return jsonify({'menu_items': menu_items[index][date]})
  return jsonify({'menu_items': menu_items})

if __name__ == '__main__':
  app.run(debug=True)

@app.route('/handle_upvote/<string:dining_hall>/<string:item_name>', methods=['POST'])
def handle_upvote(dining_hall, item_name):
  pass

@app.route('/handle_downvote/<string:dining_hall>/<string:item_name>', methods=['POST'])
def handle_downvote(dining_hall, item_name):
  pass

父组件添加自动重试逻辑

假设MainLayout的父组件是Home,示例代码如下:

import { useState, useEffect } from 'react';
import MainLayout from './MainLayout';
import { fetchMenuData } from './api';

const Home = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [menuData, setMenuData] = useState<any>(null);

  const loadMenuData = async () => {
    setIsLoading(true);
    const data = await fetchMenuData('J2 Dining', 'Wednesday, June 31');
    setMenuData(data);
    setIsLoading(false);
  };

  useEffect(() => {
    // 初始加载数据
    loadMenuData();
    // 若数据未获取到,每5秒重试一次
    const retryInterval = setInterval(() => {
      if (!menuData) {
        loadMenuData();
      } else {
        clearInterval(retryInterval);
      }
    }, 5000);

    return () => clearInterval(retryInterval);
  }, [menuData]);

  return <MainLayout isLoading={isLoading} menuData={menuData} />;
};

export default Home;

额外优化建议

  • 开发环境中,可在Next.js启动时检测Flask服务状态,未就绪时显示加载提示直到服务可用。
  • 生产环境中,建议使用服务发现工具(如AWS Cloud Map)或负载均衡器,确保Next.js请求时Flask服务已完全就绪。

内容的提问来源于stack exchange,提问作者A.K Krishnamurthy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 03:49:50