next.js/crates/next-api/src/pages.rs
pages.rs1790 lines67.9 KB
use anyhow::{Context, Result, bail};
use bincode::{Decode, Encode};
use futures::future::BoxFuture;
use next_core::{
    PageLoaderAsset, create_page_loader_entry_module, get_asset_path_from_pathname,
    get_edge_resolve_options_context,
    hmr_entry::HmrEntryModule,
    mode::NextMode,
    next_client::{
        ClientContextType, get_client_module_options_context, get_client_resolve_options_context,
        get_client_runtime_entries,
    },
    next_dynamic::NextDynamicTransition,
    next_edge::route_regex::get_named_middleware_regex,
    next_manifests::{
        BuildManifest, ClientBuildManifest, EdgeFunctionDefinition, MiddlewaresManifestV2,
        PagesManifest, ProxyMatcher, Regions,
    },
    next_pages::create_page_ssr_entry_module,
    next_server::{
        ServerContextType, get_server_module_options_context, get_server_resolve_options_context,
    },
    pages_structure::{
        PagesDirectoryStructure, PagesStructure, PagesStructureItem, find_pages_structure,
    },
    parse_segment_config_from_source,
    segment_config::ParseSegmentMode,
    util::{NextRuntime, get_asset_prefix_from_pathname, pages_function_name},
};
use tracing::Instrument;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
    Completion, FxIndexMap, NonLocalValue, ResolvedVc, TaskInput, ValueToString, Vc, fxindexmap,
    fxindexset, trace::TraceRawVcs,
};
use turbo_tasks_fs::{
    self, File, FileContent, FileSystem, FileSystemPath, FileSystemPathOption, VirtualFileSystem,
};
use turbopack::{
    ModuleAssetContext,
    module_options::ModuleOptionsContext,
    transition::{FullContextTransition, Transition, TransitionOptions},
};
use turbopack_core::{
    asset::AssetContent,
    chunk::{
        ChunkGroupResult, ChunkingContext, ChunkingContextExt, EvaluatableAsset, EvaluatableAssets,
        availability_info::AvailabilityInfo,
    },
    context::AssetContext,
    file_source::FileSource,
    ident::{AssetIdent, Layer},
    module::Module,
    module_graph::{
        GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
        binding_usage_info::compute_binding_usage_info,
        chunk_group_info::{ChunkGroup, ChunkGroupEntry},
    },
    output::{OptionOutputAsset, OutputAsset, OutputAssets},
    reference::all_assets_from_entries,
    reference_type::{EcmaScriptModulesReferenceSubType, EntryReferenceSubType, ReferenceType},
    resolve::{ResolveErrorMode, origin::PlainResolveOrigin, parse::Request, pattern::Pattern},
    source::Source,
    virtual_output::VirtualOutputAsset,
};
use turbopack_nodejs::NodeJsChunkingContext;
use turbopack_resolve::{ecmascript::esm_resolve, resolve_options_context::ResolveOptionsContext};

use crate::{
    dynamic_imports::{
        DynamicImportedChunks, NextDynamicChunkAvailability, collect_next_dynamic_chunks,
    },
    font::FontManifest,
    loadable_manifest::create_react_loadable_manifest,
    module_graph::{NextDynamicGraphs, validate_pages_css_imports},
    nft_json::NftJsonAsset,
    paths::{
        all_asset_paths, all_paths_in_root, get_asset_paths_from_root, get_js_paths_from_root,
        get_wasm_paths_from_root, paths_to_bindings, wasm_paths_to_bindings,
    },
    project::Project,
    route::{Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs, Route, Routes},
    sri_manifest::get_sri_manifest_asset,
};

#[turbo_tasks::value]
pub struct PagesProject {
    project: ResolvedVc<Project>,
}

#[turbo_tasks::value_impl]
impl PagesProject {
    #[turbo_tasks::function]
    pub fn new(project: ResolvedVc<Project>) -> Vc<Self> {
        PagesProject { project }.cell()
    }

    #[turbo_tasks::function]
    pub async fn routes(self: Vc<Self>) -> Result<Vc<Routes>> {
        let pages_structure = self.pages_structure();
        let PagesStructure {
            api,
            pages,
            app: _,
            document: _,
            error: _,
            error_500: _,
            has_user_pages: _,
            should_create_pages_entries,
        } = &*pages_structure.await?;
        let mut routes = FxIndexMap::default();

        // If pages entries shouldn't be created (build mode with no pages), return empty routes
        if !should_create_pages_entries {
            return Ok(Vc::cell(routes));
        }

        async fn add_page_to_routes(
            routes: &mut FxIndexMap<RcStr, Route>,
            page: Vc<PagesStructureItem>,
            make_route: impl Fn(
                RcStr,
                RcStr,
                Vc<PagesStructureItem>,
            ) -> BoxFuture<'static, Result<Route>>,
        ) -> Result<()> {
            let PagesStructureItem {
                next_router_path,
                original_path,
                ..
            } = &*page.await?;
            let pathname: RcStr = format!("/{}", next_router_path.path).into();
            let original_name = format!("/{}", original_path.path).into();
            let route = make_route(pathname.clone(), original_name, page).await?;
            routes.insert(pathname, route);
            Ok(())
        }

        async fn add_dir_to_routes(
            routes: &mut FxIndexMap<RcStr, Route>,
            dir: Vc<PagesDirectoryStructure>,
            make_route: impl Fn(
                RcStr,
                RcStr,
                Vc<PagesStructureItem>,
            ) -> BoxFuture<'static, Result<Route>>,
        ) -> Result<()> {
            let mut queue = vec![dir];
            while let Some(dir) = queue.pop() {
                let PagesDirectoryStructure {
                    ref items,
                    ref children,
                    next_router_path: _,
                    project_path: _,
                } = *dir.await?;
                for &item in items.iter() {
                    add_page_to_routes(routes, *item, &make_route).await?;
                }
                for &child in children.iter() {
                    queue.push(*child);
                }
            }
            Ok(())
        }

        if let Some(api) = *api {
            add_dir_to_routes(&mut routes, *api, |pathname, original_name, page| {
                Box::pin(async move {
                    Ok(Route::PageApi {
                        endpoint: ResolvedVc::upcast(
                            PageEndpoint::new(
                                PageEndpointType::Api,
                                self,
                                pathname,
                                original_name,
                                page,
                                pages_structure,
                            )
                            .to_resolved()
                            .await?,
                        ),
                    })
                })
            })
            .await?;
        }

        let make_page_route = |pathname: RcStr, original_name: RcStr, page| -> BoxFuture<_> {
            Box::pin(async move {
                Ok(Route::Page {
                    html_endpoint: ResolvedVc::upcast(
                        PageEndpoint::new(
                            PageEndpointType::Html,
                            self,
                            pathname.clone(),
                            original_name.clone(),
                            page,
                            pages_structure,
                        )
                        .to_resolved()
                        .await?,
                    ),
                    // The data endpoint is only needed in development mode to support HMR
                    data_endpoint: if self.project().next_mode().await?.is_development() {
                        Some(ResolvedVc::upcast(
                            PageEndpoint::new(
                                PageEndpointType::Data,
                                self,
                                pathname,
                                original_name,
                                page,
                                pages_structure,
                            )
                            .to_resolved()
                            .await?,
                        ))
                    } else {
                        None
                    },
                })
            })
        };

        if let Some(pages) = *pages {
            add_dir_to_routes(&mut routes, *pages, make_page_route).await?;
        }

        Ok(Vc::cell(routes))
    }

    #[turbo_tasks::function]
    async fn to_endpoint(
        self: Vc<Self>,
        item: Vc<PagesStructureItem>,
        ty: PageEndpointType,
    ) -> Result<Vc<Box<dyn Endpoint>>> {
        let PagesStructureItem {
            next_router_path,
            original_path,
            ..
        } = &*item.await?;
        let pathname: RcStr = format!("/{}", next_router_path.path).into();
        let original_name = format!("/{}", original_path.path).into();
        let endpoint = Vc::upcast(PageEndpoint::new(
            ty,
            self,
            pathname,
            original_name,
            item,
            self.pages_structure(),
        ));
        Ok(endpoint)
    }

    #[turbo_tasks::function]
    pub async fn document_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
        Ok(self.to_endpoint(
            *self.pages_structure().await?.document,
            PageEndpointType::SsrOnly,
        ))
    }

    #[turbo_tasks::function]
    pub async fn app_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
        Ok(self.to_endpoint(*self.pages_structure().await?.app, PageEndpointType::Html))
    }

    #[turbo_tasks::function]
    pub async fn error_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
        Ok(self.to_endpoint(*self.pages_structure().await?.error, PageEndpointType::Html))
    }

    #[turbo_tasks::function]
    fn project(&self) -> Vc<Project> {
        *self.project
    }

    #[turbo_tasks::function]
    async fn pages_structure(&self) -> Result<Vc<PagesStructure>> {
        let next_router_fs = Vc::upcast::<Box<dyn FileSystem>>(VirtualFileSystem::new());
        let next_router_root = next_router_fs.root().owned().await?;
        Ok(find_pages_structure(
            self.project.project_path().owned().await?,
            next_router_root,
            self.project.next_config().page_extensions(),
            self.project.next_mode(),
        ))
    }

    #[turbo_tasks::function]
    async fn pages_dir(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
        Ok(if let Some(pages) = self.pages_structure().await?.pages {
            pages.project_path()
        } else {
            self.project().project_path().await?.join("pages")?.cell()
        })
    }

    #[turbo_tasks::function]
    async fn client_transitions(self: Vc<Self>) -> Result<Vc<TransitionOptions>> {
        Ok(TransitionOptions {
            named_transitions: [
                (
                    rcstr!("next-dynamic"),
                    ResolvedVc::upcast(NextDynamicTransition::new_marker().to_resolved().await?),
                ),
                (
                    rcstr!("next-dynamic-client"),
                    ResolvedVc::upcast(NextDynamicTransition::new_marker().to_resolved().await?),
                ),
            ]
            .into_iter()
            .collect(),
            ..Default::default()
        }
        .cell())
    }

    #[turbo_tasks::function]
    async fn server_transitions(self: Vc<Self>) -> Result<Vc<TransitionOptions>> {
        Ok(TransitionOptions {
            named_transitions: [
                (
                    rcstr!("next-dynamic"),
                    ResolvedVc::upcast(NextDynamicTransition::new_marker().to_resolved().await?),
                ),
                (
                    rcstr!("next-dynamic-client"),
                    ResolvedVc::upcast(
                        NextDynamicTransition::new_client(self.client_transition())
                            .to_resolved()
                            .await?,
                    ),
                ),
            ]
            .into_iter()
            .collect(),
            ..Default::default()
        }
        .cell())
    }

    #[turbo_tasks::function]
    fn client_transition(self: Vc<Self>) -> Vc<Box<dyn Transition>> {
        Vc::upcast(FullContextTransition::new(self.client_module_context()))
    }

    #[turbo_tasks::function]
    async fn client_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
        Ok(get_client_module_options_context(
            self.project().project_path().owned().await?,
            self.project().execution_context(),
            self.project().client_compile_time_info().environment(),
            ClientContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            self.project().encryption_key(),
        ))
    }

    #[turbo_tasks::function]
    async fn client_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
        Ok(get_client_resolve_options_context(
            self.project().project_path().owned().await?,
            ClientContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            self.project().execution_context(),
        ))
    }

    #[turbo_tasks::function]
    pub(super) fn client_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
        ModuleAssetContext::new(
            self.client_transitions(),
            self.project().client_compile_time_info(),
            self.client_module_options_context(),
            self.client_resolve_options_context(),
            Layer::new_with_user_friendly_name(rcstr!("client"), rcstr!("Browser")),
        )
    }

    #[turbo_tasks::function]
    pub(super) fn ssr_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
        ModuleAssetContext::new(
            self.server_transitions(),
            self.project().server_compile_time_info(),
            self.ssr_module_options_context(),
            self.ssr_resolve_options_context(),
            Layer::new_with_user_friendly_name(rcstr!("ssr"), rcstr!("SSR")),
        )
    }

    /// Returns a context specific to pages/api.
    /// This mimics the current configuration in [next-dev](https://github.com/vercel/next.js/blob/9b4b0847ed4a1025e73bec16a9ee11766e632e14/packages/next/src/build/webpack-config.ts#L1381-L1385)
    #[turbo_tasks::function]
    pub(super) fn api_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
        ModuleAssetContext::new(
            self.server_transitions(),
            self.project().server_compile_time_info(),
            self.api_module_options_context(),
            self.ssr_resolve_options_context(),
            Layer::new_with_user_friendly_name(rcstr!("api"), rcstr!("Route")),
        )
    }

    #[turbo_tasks::function]
    pub(super) fn edge_ssr_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
        ModuleAssetContext::new(
            Default::default(),
            self.project().edge_compile_time_info(),
            self.edge_ssr_module_options_context(),
            self.edge_ssr_resolve_options_context(),
            Layer::new_with_user_friendly_name(rcstr!("edge-ssr"), rcstr!("Edge SSR")),
        )
    }

    #[turbo_tasks::function]
    pub(super) fn edge_api_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
        ModuleAssetContext::new(
            Default::default(),
            self.project().edge_compile_time_info(),
            self.edge_api_module_options_context(),
            self.edge_ssr_resolve_options_context(),
            Layer::new_with_user_friendly_name(rcstr!("edge-api"), rcstr!("Edge Route")),
        )
    }

    #[turbo_tasks::function]
    async fn ssr_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
        Ok(get_server_module_options_context(
            self.project().project_path().owned().await?,
            self.project().execution_context(),
            ServerContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            NextRuntime::NodeJs,
            self.project().encryption_key(),
            self.project().server_compile_time_info().environment(),
            self.project().client_compile_time_info().environment(),
        ))
    }

    #[turbo_tasks::function]
    async fn edge_ssr_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
        Ok(get_server_module_options_context(
            self.project().project_path().owned().await?,
            self.project().execution_context(),
            ServerContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            NextRuntime::Edge,
            self.project().encryption_key(),
            self.project().edge_compile_time_info().environment(),
            self.project().client_compile_time_info().environment(),
        ))
    }

    #[turbo_tasks::function]
    async fn api_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
        Ok(get_server_module_options_context(
            self.project().project_path().owned().await?,
            self.project().execution_context(),
            ServerContextType::PagesApi {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            NextRuntime::NodeJs,
            self.project().encryption_key(),
            self.project().server_compile_time_info().environment(),
            self.project().client_compile_time_info().environment(),
        ))
    }

    #[turbo_tasks::function]
    async fn edge_api_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
        Ok(get_server_module_options_context(
            self.project().project_path().owned().await?,
            self.project().execution_context(),
            ServerContextType::PagesApi {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            NextRuntime::Edge,
            self.project().encryption_key(),
            self.project().edge_compile_time_info().environment(),
            self.project().client_compile_time_info().environment(),
        ))
    }

    #[turbo_tasks::function]
    async fn ssr_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
        Ok(get_server_resolve_options_context(
            self.project().project_path().owned().await?,
            // NOTE(alexkirsz) This could be `PagesData` for the data endpoint, but it doesn't
            // matter (for now at least) because `get_server_resolve_options_context` doesn't
            // differentiate between the two.
            ServerContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            self.project().execution_context(),
            None, // root params are not available in pages dir
        ))
    }

    #[turbo_tasks::function]
    async fn edge_ssr_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
        Ok(get_edge_resolve_options_context(
            self.project().project_path().owned().await?,
            // NOTE(alexkirsz) This could be `PagesData` for the data endpoint, but it doesn't
            // matter (for now at least) because `get_server_resolve_options_context` doesn't
            // differentiate between the two.
            ServerContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            self.project().execution_context(),
            None, // root params are not available in pages dir
        ))
    }

    #[turbo_tasks::function]
    async fn client_runtime_entries(self: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
        let client_runtime_entries = get_client_runtime_entries(
            self.project().project_path().owned().await?,
            ClientContextType::Pages {
                pages_dir: self.pages_dir().owned().await?,
            },
            self.project().next_mode(),
            self.project().next_config(),
            self.project().execution_context(),
        );
        Ok(client_runtime_entries.resolve_entries(Vc::upcast(self.client_module_context())))
    }

    #[turbo_tasks::function]
    pub async fn client_main_module(self: Vc<Self>) -> Result<Vc<Box<dyn Module>>> {
        let client_module_context = Vc::upcast(self.client_module_context());

        let client_main_module = esm_resolve(
            Vc::upcast(PlainResolveOrigin::new(
                client_module_context,
                self.project().project_path().await?.join("_")?,
            )),
            Request::parse(Pattern::Constant(
                match *self.project().next_mode().await? {
                    NextMode::Development => rcstr!("next/dist/client/next-dev-turbopack.js"),
                    NextMode::Build => rcstr!("next/dist/client/next-turbopack.js"),
                },
            )),
            EcmaScriptModulesReferenceSubType::Undefined,
            ResolveErrorMode::Error,
            None,
        )
        .await?
        .first_module()
        .await?
        .context("expected Next.js client runtime to resolve to a module")?;

        Ok(*client_main_module)
    }
}

#[turbo_tasks::value]
struct PageEndpoint {
    ty: PageEndpointType,
    pages_project: ResolvedVc<PagesProject>,
    pathname: RcStr,
    original_name: RcStr,
    page: ResolvedVc<PagesStructureItem>,
    pages_structure: ResolvedVc<PagesStructure>,
}

#[derive(
    Copy, Clone, PartialEq, Eq, Hash, Debug, TaskInput, TraceRawVcs, NonLocalValue, Encode, Decode,
)]
enum PageEndpointType {
    Api,
    Html,
    // A development only type that is used in pages router so we can differentiate between
    // components changing and server props changing.
    Data,
    // for _document.js
    SsrOnly,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, TaskInput, TraceRawVcs, Encode, Decode)]
enum SsrChunkType {
    Page,
    Data,
    Api,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
enum EmitManifests {
    /// Don't emit any manifests
    None,
    /// Emit the manifest for basic Next.js functionality (e.g. pages-manifest.json)
    Minimal,
    /// All manifests: `Minimal` plus server-reference-manifest, next/font, next/dynamic
    Full,
}

#[turbo_tasks::value_impl]
impl PageEndpoint {
    #[turbo_tasks::function]
    fn new(
        ty: PageEndpointType,
        pages_project: ResolvedVc<PagesProject>,
        pathname: RcStr,
        original_name: RcStr,
        page: ResolvedVc<PagesStructureItem>,
        pages_structure: ResolvedVc<PagesStructure>,
    ) -> Vc<Self> {
        PageEndpoint {
            ty,
            pages_project,
            pathname,
            original_name,
            page,
            pages_structure,
        }
        .cell()
    }

    #[turbo_tasks::function]
    async fn source(&self) -> Result<Vc<Box<dyn Source>>> {
        Ok(Vc::upcast(FileSource::new_with_query(
            self.page.file_path().owned().await?,
            // When creating a data endpoint we also create an Html endpoint for the same source
            // So add a query parameter to differentiate between the two.
            if self.ty == PageEndpointType::Data {
                rcstr!("?server-data")
            } else {
                rcstr!("")
            },
        )))
    }

    #[turbo_tasks::function]
    async fn client_module(self: Vc<Self>) -> Result<Vc<Box<dyn Module>>> {
        let this = self.await?;
        let page_loader = create_page_loader_entry_module(
            Vc::upcast(this.pages_project.client_module_context()),
            self.source(),
            this.pathname.clone(),
        );
        if matches!(
            *this.pages_project.project().next_mode().await?,
            NextMode::Development
        ) && let Some(chunkable) = ResolvedVc::try_downcast(page_loader.to_resolved().await?)
        {
            return Ok(Vc::upcast(HmrEntryModule::new(
                AssetIdent::from_path(this.page.await?.base_path.clone()),
                *chunkable,
            )));
        }
        Ok(page_loader)
    }

    #[turbo_tasks::function]
    async fn client_evaluatable_assets(self: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
        let this = self.await?;

        let client_module = self.client_module();
        let client_main_module = this.pages_project.client_main_module();

        let Some(client_module) = ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(
            client_module.to_resolved().await?,
        ) else {
            bail!("expected an evaluateable asset");
        };

        let Some(client_main_module) = ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(
            client_main_module.to_resolved().await?,
        ) else {
            bail!("expected an evaluateable asset");
        };

        let evaluatable_assets = this
            .pages_project
            .client_runtime_entries()
            .with_entry(*client_main_module)
            .with_entry(*client_module);
        Ok(evaluatable_assets)
    }

    #[turbo_tasks::function]
    async fn client_module_graph(self: Vc<Self>) -> Result<Vc<ModuleGraph>> {
        let this = self.await?;
        let project = this.pages_project.project();
        let evaluatable_assets = self.client_evaluatable_assets();
        Ok(project.module_graph_for_modules(evaluatable_assets))
    }

    #[turbo_tasks::function]
    async fn ssr_module_graph(self: Vc<Self>) -> Result<Vc<ModuleGraph>> {
        let this = self.await?;
        let project = this.pages_project.project();

        if *project.per_page_module_graph().await? {
            let next_mode = project.next_mode();
            let next_mode_ref = next_mode.await?;
            let should_trace = next_mode_ref.is_production();
            let should_read_binding_usage = next_mode_ref.is_production();

            let ssr_chunk_module = self.internal_ssr_chunk_module().await?;
            // Implements layout segment optimization to compute a graph "chain" for document, app,
            // page
            let mut graphs = vec![];
            let mut visited_modules = VisitedModules::empty();
            for module in [
                ssr_chunk_module.document_module,
                ssr_chunk_module.app_module,
            ]
            .into_iter()
            .flatten()
            {
                let graph = SingleModuleGraph::new_with_entries_visited_intern(
                    vec![ChunkGroupEntry::Shared(module)],
                    visited_modules,
                    should_trace,
                    should_read_binding_usage,
                );
                graphs.push(graph);
                visited_modules = VisitedModules::concatenate(visited_modules, graph);
            }

            let graph = SingleModuleGraph::new_with_entries_visited_intern(
                vec![ChunkGroupEntry::Entry(vec![ssr_chunk_module.ssr_module])],
                visited_modules,
                should_trace,
                should_read_binding_usage,
            );
            graphs.push(graph);

            let remove_unused_imports = *project
                .next_config()
                .turbopack_remove_unused_imports(next_mode)
                .await?;

            let graph = if remove_unused_imports {
                let graph = ModuleGraph::from_graphs(graphs.clone(), None);
                let binding_usage_info = compute_binding_usage_info(graph, true);
                ModuleGraph::from_graphs(graphs, Some(binding_usage_info))
            } else {
                ModuleGraph::from_graphs(graphs, None)
            };

            Ok(graph.connect())
        } else {
            Ok(*project.whole_app_module_graphs().await?.full)
        }
    }

    #[turbo_tasks::function]
    async fn client_chunk_group(self: Vc<Self>) -> Result<Vc<ChunkGroupResult>> {
        async move {
            let this = self.await?;

            let project = this.pages_project.project();
            let client_chunking_context = project.client_chunking_context();

            let module_graph = self.client_module_graph();

            let evaluatable_assets = self
                .client_evaluatable_assets()
                .await?
                .iter()
                .map(|m| ResolvedVc::upcast(*m))
                .collect();
            let client_chunk_group = client_chunking_context.evaluated_chunk_group(
                AssetIdent::from_path(this.page.await?.base_path.clone()),
                ChunkGroup::Entry(evaluatable_assets),
                module_graph,
                AvailabilityInfo::root(),
            );

            Ok(client_chunk_group)
        }
        .instrument(tracing::info_span!("page client side rendering"))
        .await
    }

    #[turbo_tasks::function]
    async fn page_loader(
        self: Vc<Self>,
        client_chunks: Vc<OutputAssets>,
    ) -> Result<Vc<Box<dyn OutputAsset>>> {
        let this = self.await?;
        let project = this.pages_project.project();
        let node_root = project.client_root().owned().await?;
        let client_relative_path = self.client_relative_path();
        // In development mode, don't include a content hash and put the chunk at e.g.
        // `static/chunks/pages/page2.js`, so that the dev runtime can request it at a known path.
        // https://github.com/vercel/next.js/blob/84873e00874e096e6c4951dcf070e8219ed414e5/packages/next/src/client/route-loader.ts#L256-L271
        let use_fixed_path = this
            .pages_project
            .project()
            .next_mode()
            .await?
            .is_development();
        let page_loader = PageLoaderAsset::new(
            node_root,
            this.pathname.clone(),
            client_relative_path,
            client_chunks,
            project.client_chunking_context(),
            use_fixed_path,
        );
        Ok(Vc::upcast(page_loader))
    }

    #[turbo_tasks::function]
    async fn internal_ssr_chunk_module(self: Vc<Self>) -> Result<Vc<InternalSsrChunkModule>> {
        let this = self.await?;

        let (reference_type, project_root, module_context, edge_module_context) = match this.ty {
            PageEndpointType::Html | PageEndpointType::SsrOnly => (
                ReferenceType::Entry(EntryReferenceSubType::Page),
                this.pages_project.project().project_path().owned().await?,
                this.pages_project.ssr_module_context(),
                this.pages_project.edge_ssr_module_context(),
            ),
            PageEndpointType::Data => (
                ReferenceType::Entry(EntryReferenceSubType::PageData),
                this.pages_project.project().project_path().owned().await?,
                this.pages_project.ssr_module_context(),
                this.pages_project.edge_ssr_module_context(),
            ),
            PageEndpointType::Api => (
                ReferenceType::Entry(EntryReferenceSubType::PagesApi),
                this.pages_project.project().project_path().owned().await?,
                this.pages_project.api_module_context(),
                this.pages_project.edge_api_module_context(),
            ),
        };

        let config =
            parse_segment_config_from_source(self.source(), ParseSegmentMode::Base).await?;

        let runtime = config.runtime.unwrap_or(NextRuntime::NodeJs);

        Ok(
            // `/_app` and `/_document` never get rendered directly so they don't need to be
            // wrapped in the route module, and don't need to be handled as edge runtime as the
            // rendering for edge is part of the page bundle.
            if this.pathname == "/_app" || this.pathname == "/_document" {
                let ssr_module = module_context
                    .process(self.source(), reference_type)
                    .module();
                InternalSsrChunkModule {
                    ssr_module: ssr_module.to_resolved().await?,
                    app_module: None,
                    document_module: None,
                    // /_app and /_document are always rendered for Node.js for this case. For edge
                    // they're included in the page bundle.
                    runtime: NextRuntime::NodeJs,
                    regions: config.preferred_region.clone(),
                }
            } else {
                let modules = create_page_ssr_entry_module(
                    this.pathname.clone(),
                    reference_type,
                    project_root,
                    if runtime == NextRuntime::Edge {
                        Vc::upcast(edge_module_context)
                    } else {
                        Vc::upcast(module_context)
                    },
                    self.source(),
                    this.original_name.clone(),
                    *this.pages_structure,
                    this.pages_project.project().next_config(),
                    runtime,
                )
                .await?;

                InternalSsrChunkModule {
                    ssr_module: modules.ssr_module,
                    app_module: modules.app_module,
                    document_module: modules.document_module,
                    runtime,
                    regions: config.preferred_region.clone(),
                }
            }
            .cell(),
        )
    }

    #[turbo_tasks::function]
    async fn internal_ssr_chunk(
        self: Vc<Self>,
        ty: SsrChunkType,
        emit_manifests: EmitManifests,
        node_path: FileSystemPath,
        node_chunking_context: Vc<NodeJsChunkingContext>,
        edge_chunking_context: Vc<Box<dyn ChunkingContext>>,
    ) -> Result<Vc<SsrChunk>> {
        async move {
            let this = self.await?;

            let InternalSsrChunkModule {
                ssr_module,
                app_module,
                document_module,
                runtime,
                ref regions,
            } = *self.internal_ssr_chunk_module().await?;

            let project = this.pages_project.project();
            // The SSR and Client Graphs are not connected in Pages Router.
            // We are only interested in get_next_dynamic_imports_for_endpoint at the
            // moment, which only needs the client graph anyway.
            let ssr_module_graph = self.ssr_module_graph();

            let next_dynamic_imports = if let PageEndpointType::Html = this.ty {
                let client_availability_info = self.client_chunk_group().await?.availability_info;

                let client_module_graph = self.client_module_graph();
                let per_page_module_graph = *project.per_page_module_graph().await?;

                // We only validate the global css imports when there is not a `app` folder at the
                // root of the project.
                if project.app_project().await?.is_none() {
                    // We recreate the app_module here because the one provided from the
                    // `internal_ssr_chunk_module` is not the same as the one
                    // provided from the `client_module_graph`. There can be cases where
                    // the `app_module` is None, and we are processing the `pages/_app.js` file
                    // as a page rather than the app module.
                    let app_module = project
                        .pages_project()
                        .client_module_context()
                        .process(
                            Vc::upcast(FileSource::new(
                                this.pages_structure.await?.app.file_path().owned().await?,
                            )),
                            ReferenceType::Entry(EntryReferenceSubType::Page),
                        )
                        .to_resolved()
                        .await?
                        .module();

                    validate_pages_css_imports(
                        client_module_graph,
                        per_page_module_graph,
                        self.client_module(),
                        app_module,
                    )
                    .await?;
                }

                let next_dynamic_imports =
                    NextDynamicGraphs::new(client_module_graph, per_page_module_graph)
                        .get_next_dynamic_imports_for_endpoint(self.client_module())
                        .await?;
                Some((next_dynamic_imports, client_availability_info))
            } else {
                None
            };

            let dynamic_import_entries = if let Some((
                next_dynamic_imports,
                client_availability_info,
            )) = next_dynamic_imports
            {
                collect_next_dynamic_chunks(
                    self.client_module_graph(),
                    project.client_chunking_context(),
                    next_dynamic_imports,
                    NextDynamicChunkAvailability::AvailabilityInfo(client_availability_info),
                )
                .await?
            } else {
                DynamicImportedChunks::default().resolved_cell()
            };

            let chunking_context: Vc<Box<dyn ChunkingContext>> = match runtime {
                NextRuntime::NodeJs => Vc::upcast(node_chunking_context),
                NextRuntime::Edge => edge_chunking_context,
            };

            let mut current_chunk_group = ChunkGroupResult::empty_resolved();
            for layout in [document_module, app_module].iter().flatten().copied() {
                let span = tracing::trace_span!(
                    "layout segment",
                    name = display(layout.ident().to_string().await?)
                );
                async {
                    let chunk_group = chunking_context.chunk_group(
                        layout.ident(),
                        ChunkGroup::Shared(layout),
                        ssr_module_graph,
                        current_chunk_group.await?.availability_info,
                    );

                    current_chunk_group = current_chunk_group
                        .concatenate(chunk_group)
                        .to_resolved()
                        .await?;

                    anyhow::Ok(())
                }
                .instrument(span)
                .await?;
            }

            let is_edge = matches!(runtime, NextRuntime::Edge);
            if is_edge {
                let chunk_assets = edge_chunking_context.evaluated_chunk_group_assets(
                    ssr_module.ident(),
                    ChunkGroup::Entry(vec![ssr_module]),
                    ssr_module_graph,
                    current_chunk_group.await?.availability_info,
                );

                let chunk_assets = current_chunk_group
                    .output_assets_with_referenced()
                    .concatenate(chunk_assets)
                    .to_resolved()
                    .await?;

                Ok(SsrChunk::Edge {
                    assets: chunk_assets.primary_assets().to_resolved().await?,
                    referenced_assets: chunk_assets.referenced_assets().to_resolved().await?,
                    dynamic_import_entries,
                    regions: regions.clone(),
                }
                .cell())
            } else {
                let pathname = &this.pathname;

                let asset_path = get_asset_path_from_pathname(pathname, ".js");

                let ssr_entry_chunk_path_string = format!("pages{asset_path}");
                let ssr_entry_chunk_path = node_path.join(&ssr_entry_chunk_path_string)?;
                let ssr_entry_chunk = node_chunking_context
                    .entry_chunk_group_asset(
                        ssr_entry_chunk_path,
                        ChunkGroup::Entry(vec![ssr_module]),
                        ssr_module_graph,
                        current_chunk_group.primary_assets(),
                        current_chunk_group.referenced_assets(),
                        current_chunk_group.await?.availability_info,
                    )
                    .to_resolved()
                    .await?;

                let server_asset_trace_file = if this
                    .pages_project
                    .project()
                    .next_mode()
                    .await?
                    .is_production()
                {
                    let additional_assets = if emit_manifests == EmitManifests::Full {
                        self.react_loadable_manifest(
                            *dynamic_import_entries,
                            project.client_chunking_context(),
                            NextRuntime::NodeJs,
                        )
                        .await?
                        .iter()
                        .map(|m| **m)
                        .collect()
                    } else {
                        vec![]
                    };

                    ResolvedVc::cell(Some(ResolvedVc::upcast(
                        NftJsonAsset::new(
                            project,
                            Some(pages_function_name(&this.original_name).into()),
                            *ssr_entry_chunk,
                            additional_assets,
                        )
                        .to_resolved()
                        .await?,
                    )))
                } else {
                    ResolvedVc::cell(None)
                };

                Ok(SsrChunk::NodeJs {
                    entry: ssr_entry_chunk,
                    dynamic_import_entries,
                    server_asset_trace_file,
                }
                .cell())
            }
        }
        .instrument(match ty {
            SsrChunkType::Page => tracing::info_span!("page server side rendering"),
            SsrChunkType::Data => tracing::info_span!("server side data"),
            SsrChunkType::Api => tracing::info_span!("server side api"),
        })
        .await
    }

    #[turbo_tasks::function]
    async fn ssr_chunk(self: Vc<Self>, emit_manifests: EmitManifests) -> Result<Vc<SsrChunk>> {
        let this = self.await?;
        let project = this.pages_project.project();
        Ok(self.internal_ssr_chunk(
            SsrChunkType::Page,
            emit_manifests,
            this.pages_project
                .project()
                .node_root()
                .await?
                .join("server")?,
            project.server_chunking_context(true),
            project.edge_chunking_context(true),
        ))
    }

    #[turbo_tasks::function]
    async fn ssr_data_chunk(self: Vc<Self>, emit_manifests: EmitManifests) -> Result<Vc<SsrChunk>> {
        let this = self.await?;
        Ok(self.internal_ssr_chunk(
            SsrChunkType::Data,
            emit_manifests,
            this.pages_project
                .project()
                .node_root()
                .await?
                .join("server/data")?,
            this.pages_project.project().server_chunking_context(true),
            this.pages_project.project().edge_chunking_context(true),
        ))
    }

    #[turbo_tasks::function]
    async fn api_chunk(self: Vc<Self>, emit_manifests: EmitManifests) -> Result<Vc<SsrChunk>> {
        let this = self.await?;
        Ok(self.internal_ssr_chunk(
            SsrChunkType::Api,
            emit_manifests,
            this.pages_project
                .project()
                .node_root()
                .await?
                .join("server")?,
            this.pages_project.project().server_chunking_context(false),
            this.pages_project.project().edge_chunking_context(false),
        ))
    }

    #[turbo_tasks::function]
    async fn pages_manifest(
        &self,
        entry_chunk: Vc<Box<dyn OutputAsset>>,
    ) -> Result<Vc<Box<dyn OutputAsset>>> {
        let node_root = self.pages_project.project().node_root().await?;

        // Check if we should include pages in the manifest
        let pages_structure = self.pages_structure.await?;
        let pages = if pages_structure.should_create_pages_entries {
            let chunk_path = entry_chunk.path().await?;
            let asset_path = node_root
                .join("server")?
                .get_path_to(&chunk_path)
                .context("ssr chunk entry path must be inside the node root")?;
            [(self.pathname.clone(), asset_path.into())]
                .into_iter()
                .collect()
        } else {
            FxIndexMap::default() // Empty pages when no user pages should be created
        };

        let pages_manifest = PagesManifest { pages };
        let manifest_path_prefix = get_asset_prefix_from_pathname(&self.pathname);
        let asset = Vc::upcast(VirtualOutputAsset::new(
            node_root.join(&format!(
                "server/pages{manifest_path_prefix}/pages-manifest.json",
            ))?,
            AssetContent::file(
                FileContent::Content(File::from(serde_json::to_string_pretty(&pages_manifest)?))
                    .cell(),
            ),
        ));
        Ok(asset)
    }

    #[turbo_tasks::function]
    async fn react_loadable_manifest(
        &self,
        dynamic_import_entries: Vc<DynamicImportedChunks>,
        chunking_context: Vc<Box<dyn ChunkingContext>>,
        runtime: NextRuntime,
    ) -> Result<Vc<OutputAssets>> {
        let node_root = self.pages_project.project().node_root().owned().await?;
        let client_relative_path = self
            .pages_project
            .project()
            .client_relative_path()
            .owned()
            .await?;
        let loadable_path_prefix = get_asset_prefix_from_pathname(&self.pathname);
        Ok(create_react_loadable_manifest(
            dynamic_import_entries,
            chunking_context,
            client_relative_path,
            node_root.join(&format!(
                "server/pages{loadable_path_prefix}/react-loadable-manifest",
            ))?,
            runtime,
        ))
    }

    #[turbo_tasks::function]
    async fn build_manifest(
        &self,
        client_chunks: ResolvedVc<OutputAssets>,
    ) -> Result<Vc<Box<dyn OutputAsset>>> {
        let node_root = self.pages_project.project().node_root().owned().await?;
        let client_relative_path = self
            .pages_project
            .project()
            .client_relative_path()
            .owned()
            .await?;

        // Check if we should include pages in the manifest
        let pages_structure = self.pages_structure.await?;
        let pages = if pages_structure.should_create_pages_entries {
            fxindexmap!(self.pathname.clone() => client_chunks)
        } else {
            fxindexmap![] // Empty pages when no user pages should be created
        };

        let manifest_path_prefix = get_asset_prefix_from_pathname(&self.pathname);
        let build_manifest = BuildManifest {
            output_path: node_root.join(&format!(
                "server/pages{manifest_path_prefix}/build-manifest.json",
            ))?,
            client_relative_path,
            pages,
            polyfill_files: Default::default(),
            root_main_files: Default::default(),
        };
        Ok(Vc::upcast(build_manifest.cell()))
    }

    #[turbo_tasks::function]
    async fn client_build_manifest(
        &self,
        page_loader: ResolvedVc<Box<dyn OutputAsset>>,
    ) -> Result<Vc<Box<dyn OutputAsset>>> {
        let node_root = self.pages_project.project().node_root().await?;
        let client_relative_path = self
            .pages_project
            .project()
            .client_relative_path()
            .owned()
            .await?;

        // Check if we should include pages in the manifest
        let pages_structure = self.pages_structure.await?;
        let pages = if pages_structure.should_create_pages_entries {
            fxindexmap!(self.pathname.clone() => page_loader)
        } else {
            fxindexmap![] // Empty pages when no user pages should be created
        };

        let manifest_path_prefix = get_asset_prefix_from_pathname(&self.pathname);
        let client_build_manifest = ClientBuildManifest {
            output_path: node_root.join(&format!(
                "server/pages{manifest_path_prefix}/client-build-manifest.json",
            ))?,
            client_relative_path,
            pages,
        };

        Ok(Vc::upcast(client_build_manifest.cell()))
    }

    #[turbo_tasks::function]
    async fn output(self: Vc<Self>) -> Result<Vc<PageEndpointOutput>> {
        let this = self.await?;

        let mut server_assets = vec![];
        let mut client_assets = vec![];

        let emit_manifests = match this.ty {
            PageEndpointType::Html | PageEndpointType::SsrOnly => EmitManifests::Full,
            PageEndpointType::Api => EmitManifests::Minimal,
            PageEndpointType::Data => EmitManifests::None,
        };

        let ssr_chunk = match this.ty {
            PageEndpointType::Html => {
                let client_chunk_group = self.client_chunk_group();
                client_assets.extend(client_chunk_group.all_assets().await?.iter().copied());
                let client_chunks = *client_chunk_group.await?.assets;

                let build_manifest = self.build_manifest(client_chunks).to_resolved().await?;
                let page_loader = self.page_loader(client_chunks).to_resolved().await?;
                let client_build_manifest = self
                    .client_build_manifest(*page_loader)
                    .to_resolved()
                    .await?;
                client_assets.push(page_loader);
                server_assets.push(build_manifest);
                server_assets.push(client_build_manifest);

                self.ssr_chunk(emit_manifests)
            }

            PageEndpointType::Data => self.ssr_data_chunk(emit_manifests),
            PageEndpointType::Api => self.api_chunk(emit_manifests),
            PageEndpointType::SsrOnly => self.ssr_chunk(emit_manifests),
        };

        let client_assets: ResolvedVc<OutputAssets> = ResolvedVc::cell(client_assets);

        let manifest_path_prefix = get_asset_prefix_from_pathname(&this.pathname);
        let node_root = this.pages_project.project().node_root().owned().await?;

        if emit_manifests == EmitManifests::Full {
            let next_font_manifest_output = ResolvedVc::upcast(
                FontManifest {
                    client_root: this.pages_project.project().client_root().owned().await?,
                    node_root: node_root.clone(),
                    dir: this.pages_project.pages_dir().owned().await?,
                    original_name: this.original_name.clone(),
                    manifest_path_prefix: manifest_path_prefix.clone().into(),
                    pathname: this.pathname.clone(),
                    client_assets,
                    app_dir: false,
                }
                .resolved_cell(),
            );
            server_assets.push(next_font_manifest_output);
        }

        let page_output = match *ssr_chunk.await? {
            SsrChunk::NodeJs {
                entry,
                dynamic_import_entries,
                server_asset_trace_file,
            } => {
                // Only include the actual SSR entry chunk if pages should be created
                let pages_structure = this.pages_structure.await?;
                if pages_structure.should_create_pages_entries {
                    server_assets.push(entry);
                    if let Some(server_asset_trace_file) = &*server_asset_trace_file.await? {
                        server_assets.push(*server_asset_trace_file);
                    }
                }

                if emit_manifests != EmitManifests::None {
                    let pages_manifest = self.pages_manifest(*entry).to_resolved().await?;
                    server_assets.push(pages_manifest);
                }
                if emit_manifests == EmitManifests::Full {
                    let loadable_manifest_output = self.react_loadable_manifest(
                        *dynamic_import_entries,
                        this.pages_project.project().client_chunking_context(),
                        NextRuntime::NodeJs,
                    );
                    server_assets.extend(loadable_manifest_output.await?.iter().copied());
                }

                PageEndpointOutput::NodeJs {
                    entry_chunk: entry,
                    server_assets: ResolvedVc::cell(server_assets),
                    client_assets,
                }
            }
            SsrChunk::Edge {
                assets,
                referenced_assets,
                dynamic_import_entries,
                ref regions,
            } => {
                let node_root = this.pages_project.project().node_root().owned().await?;
                if emit_manifests != EmitManifests::None {
                    // the next-edge-ssr-loader templates expect the manifests to be stored in
                    // global variables defined in these files
                    //
                    // they are created in `setup-dev-bundler.ts`
                    let mut file_paths_from_root = if emit_manifests == EmitManifests::Full {
                        fxindexset![
                            rcstr!("server/server-reference-manifest.js"),
                            rcstr!("server/middleware-build-manifest.js"),
                            rcstr!("server/next-font-manifest.js"),
                        ]
                    } else {
                        fxindexset![]
                    };

                    if this
                        .pages_project
                        .project()
                        .next_mode()
                        .await?
                        .is_production()
                    {
                        file_paths_from_root.insert(rcstr!("required-server-files.js"));
                    }

                    let all_assets = assets.concatenate(*referenced_assets);
                    let assets_ref = assets.await?;

                    server_assets.extend(referenced_assets.await?.iter().copied());

                    // TODO(sokra): accessing the 1st asset is a bit hacky, we should find a better
                    // way to get the main entry asset
                    if let Some(&file) = assets_ref.first() {
                        let pages_manifest = self.pages_manifest(*file).to_resolved().await?;
                        server_assets.push(pages_manifest);
                    }

                    // Only include the actual edge files if pages should be created
                    let pages_structure = this.pages_structure.await?;
                    if pages_structure.should_create_pages_entries {
                        server_assets.extend(assets_ref.iter().copied());
                        file_paths_from_root
                            .extend(get_js_paths_from_root(&node_root, &assets_ref).await?);
                    }

                    if emit_manifests == EmitManifests::Full {
                        let loadable_manifest_output = self
                            .react_loadable_manifest(
                                *dynamic_import_entries,
                                this.pages_project.project().client_chunking_context(),
                                NextRuntime::Edge,
                            )
                            .await?;
                        if pages_structure.should_create_pages_entries {
                            server_assets.extend(loadable_manifest_output.iter().copied());
                            file_paths_from_root.extend(
                                get_js_paths_from_root(&node_root, &loadable_manifest_output)
                                    .await?,
                            );
                        }
                    }

                    let (wasm_paths_from_root, all_assets) =
                        if pages_structure.should_create_pages_entries {
                            let all_output_assets = all_assets_from_entries(all_assets).await?;

                            let mut wasm_paths_from_root = fxindexset![];
                            wasm_paths_from_root.extend(
                                get_wasm_paths_from_root(&node_root, &all_output_assets).await?,
                            );

                            let all_assets =
                                get_asset_paths_from_root(&node_root, &all_output_assets).await?;

                            (wasm_paths_from_root, all_assets)
                        } else {
                            (fxindexset![], vec![])
                        };

                    let named_regex = get_named_middleware_regex(&this.pathname).into();
                    let matchers = ProxyMatcher {
                        regexp: Some(named_regex),
                        original_source: this.pathname.clone(),
                        ..Default::default()
                    };
                    let regions = if let Some(regions) = regions.as_ref() {
                        if regions.len() == 1 {
                            regions
                                .first()
                                .map(|region| Regions::Single(region.clone()))
                        } else {
                            Some(Regions::Multiple(regions.clone()))
                        }
                    } else {
                        None
                    };
                    let entrypoint_asset = *assets_ref
                        .last()
                        .context("expected assets for edge pages endpoint")?;
                    let entrypoint = node_root
                        .get_path_to(&*entrypoint_asset.path().await?)
                        .context("expected edge pages asset to be within node root")?
                        .into();

                    let edge_function_definition = EdgeFunctionDefinition {
                        files: file_paths_from_root.into_iter().collect(),
                        wasm: wasm_paths_to_bindings(wasm_paths_from_root).await?,
                        assets: paths_to_bindings(all_assets),
                        name: pages_function_name(&this.original_name).into(),
                        page: this.original_name.clone(),
                        entrypoint,
                        regions,
                        matchers: vec![matchers],
                        env: this.pages_project.project().edge_env().owned().await?,
                    };
                    let middleware_manifest_v2 = MiddlewaresManifestV2 {
                        sorted_middleware: vec![this.pathname.clone()],
                        functions: [(this.pathname.clone(), edge_function_definition)]
                            .into_iter()
                            .collect(),
                        ..Default::default()
                    };
                    let manifest_path_prefix = get_asset_prefix_from_pathname(&this.pathname);
                    let middleware_manifest_v2 = VirtualOutputAsset::new(
                        node_root.join(&format!(
                            "server/pages{manifest_path_prefix}/middleware-manifest.json",
                        ))?,
                        AssetContent::file(
                            FileContent::Content(File::from(serde_json::to_string_pretty(
                                &middleware_manifest_v2,
                            )?))
                            .cell(),
                        ),
                    )
                    .to_resolved()
                    .await?;
                    server_assets.push(ResolvedVc::upcast(middleware_manifest_v2));
                }

                PageEndpointOutput::Edge {
                    files: assets,
                    server_assets: ResolvedVc::cell(server_assets),
                    client_assets,
                }
            }
        };

        Ok(page_output.cell())
    }

    #[turbo_tasks::function]
    async fn client_relative_path(&self) -> Result<Vc<FileSystemPathOption>> {
        Ok(Vc::cell(Some(
            self.pages_project
                .project()
                .client_relative_path()
                .owned()
                .await?,
        )))
    }
}

#[turbo_tasks::value]
pub struct InternalSsrChunkModule {
    pub ssr_module: ResolvedVc<Box<dyn Module>>,
    pub app_module: Option<ResolvedVc<Box<dyn Module>>>,
    pub document_module: Option<ResolvedVc<Box<dyn Module>>>,
    pub runtime: NextRuntime,
    pub regions: Option<Vec<RcStr>>,
}

#[turbo_tasks::value_impl]
impl Endpoint for PageEndpoint {
    #[turbo_tasks::function]
    async fn output(self: ResolvedVc<Self>) -> Result<Vc<EndpointOutput>> {
        let this = &*self.await?;
        let original_name = &this.original_name;
        let span = {
            match &this.ty {
                PageEndpointType::Html => {
                    tracing::info_span!("page endpoint HTML", name = display(original_name))
                }
                PageEndpointType::Data => {
                    tracing::info_span!("page endpoint data", name = display(original_name))
                }
                PageEndpointType::Api => {
                    tracing::info_span!("page endpoint API", name = display(original_name))
                }
                PageEndpointType::SsrOnly => {
                    tracing::info_span!("page endpoint SSR", name = display(original_name))
                }
            }
        };
        async move {
            let output = self.output();
            let project = this.pages_project.project();
            let node_root = project.node_root().owned().await?;
            let client_relative_root = project.client_relative_path().owned().await?;

            let output_assets = output.output_assets();
            let output_assets = if let Some(sri) =
                &*project.next_config().experimental_sri().await?
                && let Some(algorithm) = sri.algorithm.clone()
            {
                let sri_manifest = get_sri_manifest_asset(
                    node_root.join(&format!(
                        "server/pages{}/subresource-integrity-manifest.json",
                        get_asset_prefix_from_pathname(&this.pathname)
                    ))?,
                    output_assets,
                    client_relative_root.clone(),
                    algorithm,
                );
                output_assets.concat_asset(sri_manifest)
            } else {
                output_assets
            };

            let (server_paths, client_paths) = if project.next_mode().await?.is_development() {
                let server_paths = all_asset_paths(output_assets, node_root.clone(), None)
                    .owned()
                    .await?;
                let client_paths = all_paths_in_root(output_assets, client_relative_root)
                    .owned()
                    .await?;
                (server_paths, client_paths)
            } else {
                (vec![], vec![])
            };

            let written_endpoint = match *output.await? {
                PageEndpointOutput::NodeJs { entry_chunk, .. } => {
                    // Only set server_entry_path if pages should be created
                    let pages_structure = this.pages_structure.await?;
                    let server_entry_path = if pages_structure.should_create_pages_entries {
                        node_root
                            .get_path_to(&*entry_chunk.path().await?)
                            .context("ssr chunk entry path must be inside the node root")?
                            .into()
                    } else {
                        rcstr!("") // Empty path when no pages should be created
                    };

                    EndpointOutputPaths::NodeJs {
                        server_entry_path,
                        server_paths,
                        client_paths,
                    }
                }
                PageEndpointOutput::Edge { .. } => EndpointOutputPaths::Edge {
                    server_paths,
                    client_paths,
                },
            };

            anyhow::Ok(
                EndpointOutput {
                    output_assets: output_assets.to_resolved().await?,
                    output_paths: written_endpoint.resolved_cell(),
                    project: project.to_resolved().await?,
                }
                .cell(),
            )
        }
        .instrument(span)
        .await
        .with_context(|| format!("Failed to write page endpoint {}", *original_name))
    }

    #[turbo_tasks::function]
    async fn server_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
        Ok(self
            .await?
            .pages_project
            .project()
            .server_changed(self.output().server_assets()))
    }

    #[turbo_tasks::function]
    async fn client_changed(self: Vc<Self>) -> Result<Vc<Completion>> {
        Ok(self
            .await?
            .pages_project
            .project()
            .client_changed(self.output().client_assets()))
    }

    #[turbo_tasks::function]
    async fn entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
        let this = self.await?;

        let ssr_chunk_module = self.internal_ssr_chunk_module().await?;

        let shared_entries = [
            ssr_chunk_module.document_module,
            ssr_chunk_module.app_module,
        ];

        let modules = shared_entries
            .into_iter()
            .flatten()
            .map(ChunkGroupEntry::Shared)
            .chain(std::iter::once(ChunkGroupEntry::Entry(vec![
                ssr_chunk_module.ssr_module,
            ])))
            .chain(if this.ty == PageEndpointType::Html {
                Some(ChunkGroupEntry::Entry(
                    self.client_evaluatable_assets()
                        .await?
                        .iter()
                        .map(|m| ResolvedVc::upcast(*m))
                        .collect(),
                ))
                .into_iter()
            } else {
                None.into_iter()
            })
            .collect::<Vec<_>>();

        Ok(Vc::cell(modules))
    }

    #[turbo_tasks::function]
    async fn module_graphs(self: Vc<Self>) -> Result<Vc<ModuleGraphs>> {
        let client_module_graph = self.client_module_graph().to_resolved().await?;
        let ssr_module_graph = self.ssr_module_graph().to_resolved().await?;
        Ok(Vc::cell(if client_module_graph != ssr_module_graph {
            vec![client_module_graph, ssr_module_graph]
        } else {
            vec![ssr_module_graph]
        }))
    }

    #[turbo_tasks::function]
    async fn project(self: Vc<Self>) -> Result<Vc<Project>> {
        Ok(self.await?.pages_project.project())
    }
}

#[turbo_tasks::value]
enum PageEndpointOutput {
    NodeJs {
        entry_chunk: ResolvedVc<Box<dyn OutputAsset>>,
        server_assets: ResolvedVc<OutputAssets>,
        client_assets: ResolvedVc<OutputAssets>,
    },
    Edge {
        files: ResolvedVc<OutputAssets>,
        server_assets: ResolvedVc<OutputAssets>,
        client_assets: ResolvedVc<OutputAssets>,
    },
}

#[turbo_tasks::value_impl]
impl PageEndpointOutput {
    #[turbo_tasks::function]
    pub async fn output_assets(self: Vc<Self>) -> Result<Vc<OutputAssets>> {
        let server_assets = self.server_assets().await?;
        let client_assets = self.client_assets().await?;
        Ok(Vc::cell(
            server_assets
                .iter()
                .cloned()
                .chain(client_assets.iter().cloned())
                .collect(),
        ))
    }

    #[turbo_tasks::function]
    pub fn server_assets(&self) -> Vc<OutputAssets> {
        match *self {
            PageEndpointOutput::NodeJs { server_assets, .. }
            | PageEndpointOutput::Edge { server_assets, .. } => *server_assets,
        }
    }

    #[turbo_tasks::function]
    pub fn client_assets(&self) -> Vc<OutputAssets> {
        match *self {
            PageEndpointOutput::NodeJs { client_assets, .. }
            | PageEndpointOutput::Edge { client_assets, .. } => *client_assets,
        }
    }
}

#[turbo_tasks::value]
pub enum SsrChunk {
    NodeJs {
        entry: ResolvedVc<Box<dyn OutputAsset>>,
        dynamic_import_entries: ResolvedVc<DynamicImportedChunks>,
        server_asset_trace_file: ResolvedVc<OptionOutputAsset>,
    },
    Edge {
        assets: ResolvedVc<OutputAssets>,
        referenced_assets: ResolvedVc<OutputAssets>,
        dynamic_import_entries: ResolvedVc<DynamicImportedChunks>,
        regions: Option<Vec<RcStr>>,
    },
}
Quest for Codev2.0.0
/
SIGN IN