async function doRender()

in packages/runtime/src/runServerApp.tsx [127:259]


async function doRender(serverContext: ServerContext, renderOptions: RenderOptions): Promise<Response> {
  const { req, res } = serverContext;
  const {
    app,
    basename,
    serverOnlyBasename,
    createRoutes,
    documentOnly,
    disableFallback,
    assetsManifest,
    runtimeModules,
    renderMode,
    runtimeOptions,
    serverData,
  } = renderOptions;
  const finalBasename = addLeadingSlash(serverOnlyBasename || basename);
  const location = getLocation(req.url);

  const requestContext = getRequestContext(location, serverContext);
  const appConfig = getAppConfig(app);
  const routes = createRoutes({
    requestContext,
    renderMode,
  });
  let appData: AppData;
  const appContext: AppContext = {
    appExport: app,
    routes,
    appConfig,
    appData,
    loaderData: {},
    renderMode,
    assetsManifest,
    basename: finalBasename,
    matches: [],
    requestContext,
    serverData,
  };
  const runtime = new Runtime(appContext, runtimeOptions);
  runtime.setAppRouter<ServerAppRouterProps>(ServerRouter);
  // Load static module before getAppData.
  if (runtimeModules.statics) {
    await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean));
  }

  const documentData = await getDocumentData({
    loaderConfig: renderOptions.documentDataLoader,
    requestContext,
    documentOnly,
  });
  // @TODO: document should have it's own context, not shared with app.
  appContext.documentData = documentData;

  // Not to execute [getAppData] when CSR.
  if (!documentOnly) {
    try {
      appData = await getAppData(app, requestContext);
    } catch (err) {
      console.error('Error: get app data error when SSR.', err);
    }
  }

  // HashRouter loads route modules by the CSR.
  if (appConfig?.router?.type === 'hash') {
    return renderDocument({ matches: [], routes, renderOptions, documentData });
  }

  const matches = matchRoutes(routes, location, finalBasename);
  const routePath = getCurrentRoutePath(matches);
  if (documentOnly) {
    return renderDocument({ matches, routePath, routes, renderOptions, documentData });
  } else if (!matches.length) {
    return handleNotFoundResponse();
  }

  try {
    const routeModules = await loadRouteModules(matches.map(({ route: { id, lazy } }) => ({ id, lazy })));
    const loaderData = {};
    for (const routeId in routeModules) {
      const { loader } = routeModules[routeId];
      if (loader) {
        const { data, pageConfig } = await loader();
        loaderData[routeId] = {
          data,
          pageConfig,
        };
      }
    }
    const revalidate = renderMode === 'SSG' && needRevalidate(matches);
    runtime.setAppContext({ ...appContext, revalidate, routeModules, loaderData, routePath, matches, appData });
    if (runtimeModules.commons) {
      await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean));
    }
    /**
       Plugin may register response handlers, for example:
       ```
       addResponseHandler((req) => {
         if (redirect) {
           return {
             statusCode: 302,
             statusText: 'Found',
             headers: {
               location: '/redirect',
             },
           };
         }
       });
       ```
     */
    const responseHandlers = runtime.getResponseHandlers();
    for (const responseHandler of responseHandlers) {
      if (typeof responseHandler === 'function') {
        const response = await responseHandler(req, res);
        if (response) {
          return response as Response;
        }
      }
    }

    return await renderServerEntry({
      runtime,
      matches,
      location,
      renderOptions,
    });
  } catch (err) {
    if (disableFallback) {
      throw err;
    }
    console.error('Warning: render server entry error, downgrade to csr.', err);
    return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true, documentData });
  }
}