next_core/
next_config.rs

1use anyhow::{Context, Result, bail};
2use rustc_hash::FxHashSet;
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::Value as JsonValue;
5use turbo_esregex::EsRegex;
6use turbo_rcstr::RcStr;
7use turbo_tasks::{
8    FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
9    trace::TraceRawVcs,
10};
11use turbo_tasks_env::EnvMap;
12use turbopack::module_options::{
13    ConditionItem, ConditionPath, LoaderRuleItem, OptionWebpackRules,
14    module_options_context::{MdxTransformOptions, OptionWebpackConditions},
15};
16use turbopack_core::resolve::ResolveAliasMap;
17use turbopack_ecmascript::{OptionTreeShaking, TreeShakingMode};
18use turbopack_ecmascript_plugins::transform::{
19    emotion::EmotionTransformConfig, relay::RelayConfig,
20    styled_components::StyledComponentsTransformConfig,
21};
22use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
23
24use crate::{
25    mode::NextMode, next_import_map::mdx_import_source_file,
26    next_shared::transforms::ModularizeImportPackageConfig,
27};
28
29#[turbo_tasks::value]
30struct NextConfigAndCustomRoutes {
31    config: ResolvedVc<NextConfig>,
32    custom_routes: ResolvedVc<CustomRoutes>,
33}
34
35#[turbo_tasks::value]
36struct CustomRoutes {
37    rewrites: ResolvedVc<Rewrites>,
38}
39
40#[turbo_tasks::value(transparent)]
41pub struct ModularizeImports(FxIndexMap<String, ModularizeImportPackageConfig>);
42
43#[turbo_tasks::value(transparent)]
44#[derive(Clone, Debug)]
45pub struct CacheKinds(FxHashSet<RcStr>);
46
47impl CacheKinds {
48    pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
49        self.0.extend(iter);
50    }
51}
52
53impl Default for CacheKinds {
54    fn default() -> Self {
55        CacheKinds(["default", "remote"].iter().map(|&s| s.into()).collect())
56    }
57}
58
59#[turbo_tasks::value(eq = "manual")]
60#[derive(Clone, Debug, Default, PartialEq)]
61#[serde(default, rename_all = "camelCase")]
62pub struct NextConfig {
63    // TODO all fields should be private and access should be wrapped within a turbo-tasks function
64    // Otherwise changing NextConfig will lead to invalidating all tasks accessing it.
65    pub config_file: Option<RcStr>,
66    pub config_file_name: RcStr,
67
68    /// In-memory cache size in bytes.
69    ///
70    /// If `cache_max_memory_size: 0` disables in-memory caching.
71    pub cache_max_memory_size: Option<f64>,
72    /// custom path to a cache handler to use
73    pub cache_handler: Option<RcStr>,
74
75    pub env: FxIndexMap<String, JsonValue>,
76    pub experimental: ExperimentalConfig,
77    pub images: ImageConfig,
78    pub page_extensions: Vec<RcStr>,
79    pub react_production_profiling: Option<bool>,
80    pub react_strict_mode: Option<bool>,
81    pub transpile_packages: Option<Vec<RcStr>>,
82    pub modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
83    pub dist_dir: Option<RcStr>,
84    pub deployment_id: Option<RcStr>,
85    sass_options: Option<serde_json::Value>,
86    pub trailing_slash: Option<bool>,
87    pub asset_prefix: Option<RcStr>,
88    pub base_path: Option<RcStr>,
89    pub skip_middleware_url_normalize: Option<bool>,
90    pub skip_trailing_slash_redirect: Option<bool>,
91    pub i18n: Option<I18NConfig>,
92    pub cross_origin: Option<CrossOriginConfig>,
93    pub dev_indicators: Option<DevIndicatorsConfig>,
94    pub output: Option<OutputType>,
95    pub turbopack: Option<TurbopackConfig>,
96    production_browser_source_maps: bool,
97    output_file_tracing_includes: Option<serde_json::Value>,
98    output_file_tracing_excludes: Option<serde_json::Value>,
99    // TODO: This option is not respected, it uses Turbopack's root instead.
100    output_file_tracing_root: Option<RcStr>,
101
102    /// Enables the bundling of node_modules packages (externals) for pages
103    /// server-side bundles.
104    ///
105    /// [API Reference](https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies)
106    pub bundle_pages_router_dependencies: Option<bool>,
107
108    /// A list of packages that should be treated as external on the server
109    /// build.
110    ///
111    /// [API Reference](https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages)
112    pub server_external_packages: Option<Vec<RcStr>>,
113
114    #[serde(rename = "_originalRedirects")]
115    pub original_redirects: Option<Vec<Redirect>>,
116
117    // Partially supported
118    pub compiler: Option<CompilerConfig>,
119
120    pub optimize_fonts: Option<bool>,
121
122    // unsupported
123    amp: AmpConfig,
124    clean_dist_dir: bool,
125    compress: bool,
126    eslint: EslintConfig,
127    exclude_default_moment_locales: bool,
128    // this can be a function in js land
129    export_path_map: Option<serde_json::Value>,
130    // this is a function in js land
131    generate_build_id: Option<serde_json::Value>,
132    generate_etags: bool,
133    http_agent_options: HttpAgentConfig,
134    on_demand_entries: OnDemandEntriesConfig,
135    powered_by_header: bool,
136    public_runtime_config: FxIndexMap<String, serde_json::Value>,
137    server_runtime_config: FxIndexMap<String, serde_json::Value>,
138    static_page_generation_timeout: f64,
139    target: Option<String>,
140    typescript: TypeScriptConfig,
141    use_file_system_public_routes: bool,
142    webpack: Option<serde_json::Value>,
143}
144
145#[derive(
146    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
147)]
148#[serde(rename_all = "kebab-case")]
149pub enum CrossOriginConfig {
150    Anonymous,
151    UseCredentials,
152}
153
154#[derive(
155    Clone,
156    Debug,
157    Default,
158    PartialEq,
159    Serialize,
160    Deserialize,
161    TraceRawVcs,
162    NonLocalValue,
163    OperationValue,
164)]
165#[serde(rename_all = "camelCase")]
166struct AmpConfig {
167    canonical_base: Option<String>,
168}
169
170#[derive(
171    Clone,
172    Debug,
173    Default,
174    PartialEq,
175    Serialize,
176    Deserialize,
177    TraceRawVcs,
178    NonLocalValue,
179    OperationValue,
180)]
181#[serde(rename_all = "camelCase")]
182struct EslintConfig {
183    dirs: Option<Vec<String>>,
184    ignore_during_builds: Option<bool>,
185}
186
187#[derive(
188    Clone,
189    Debug,
190    Default,
191    PartialEq,
192    Serialize,
193    Deserialize,
194    TraceRawVcs,
195    NonLocalValue,
196    OperationValue,
197)]
198#[serde(rename_all = "kebab-case")]
199pub enum BuildActivityPositions {
200    #[default]
201    BottomRight,
202    BottomLeft,
203    TopRight,
204    TopLeft,
205}
206
207#[derive(
208    Clone,
209    Debug,
210    Default,
211    PartialEq,
212    Serialize,
213    Deserialize,
214    TraceRawVcs,
215    NonLocalValue,
216    OperationValue,
217)]
218#[serde(rename_all = "camelCase")]
219pub struct DevIndicatorsOptions {
220    pub build_activity_position: Option<BuildActivityPositions>,
221    pub position: Option<BuildActivityPositions>,
222}
223
224#[derive(
225    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
226)]
227#[serde(untagged)]
228pub enum DevIndicatorsConfig {
229    WithOptions(DevIndicatorsOptions),
230    Boolean(bool),
231}
232
233#[derive(
234    Clone,
235    Debug,
236    Default,
237    PartialEq,
238    Serialize,
239    Deserialize,
240    TraceRawVcs,
241    NonLocalValue,
242    OperationValue,
243)]
244#[serde(rename_all = "camelCase")]
245struct OnDemandEntriesConfig {
246    max_inactive_age: f64,
247    pages_buffer_length: f64,
248}
249
250#[derive(
251    Clone,
252    Debug,
253    Default,
254    PartialEq,
255    Serialize,
256    Deserialize,
257    TraceRawVcs,
258    NonLocalValue,
259    OperationValue,
260)]
261#[serde(rename_all = "camelCase")]
262struct HttpAgentConfig {
263    keep_alive: bool,
264}
265
266#[derive(
267    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
268)]
269#[serde(rename_all = "camelCase")]
270pub struct DomainLocale {
271    pub default_locale: String,
272    pub domain: String,
273    pub http: Option<bool>,
274    pub locales: Option<Vec<String>>,
275}
276
277#[derive(
278    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
279)]
280#[serde(rename_all = "camelCase")]
281pub struct I18NConfig {
282    pub default_locale: String,
283    pub domains: Option<Vec<DomainLocale>>,
284    pub locale_detection: Option<bool>,
285    pub locales: Vec<String>,
286}
287
288#[derive(
289    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
290)]
291#[serde(rename_all = "kebab-case")]
292pub enum OutputType {
293    Standalone,
294    Export,
295}
296
297#[derive(
298    Debug,
299    Clone,
300    Hash,
301    Eq,
302    PartialEq,
303    Ord,
304    PartialOrd,
305    TaskInput,
306    TraceRawVcs,
307    Serialize,
308    Deserialize,
309    NonLocalValue,
310    OperationValue,
311)]
312#[serde(tag = "type", rename_all = "kebab-case")]
313pub enum RouteHas {
314    Header {
315        key: RcStr,
316        #[serde(skip_serializing_if = "Option::is_none")]
317        value: Option<RcStr>,
318    },
319    Cookie {
320        key: RcStr,
321        #[serde(skip_serializing_if = "Option::is_none")]
322        value: Option<RcStr>,
323    },
324    Query {
325        key: RcStr,
326        #[serde(skip_serializing_if = "Option::is_none")]
327        value: Option<RcStr>,
328    },
329    Host {
330        value: RcStr,
331    },
332}
333
334#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
335#[serde(rename_all = "camelCase")]
336pub struct HeaderValue {
337    pub key: RcStr,
338    pub value: RcStr,
339}
340
341#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
342#[serde(rename_all = "camelCase")]
343pub struct Header {
344    pub source: String,
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub base_path: Option<bool>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub locale: Option<bool>,
349    pub headers: Vec<HeaderValue>,
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub has: Option<Vec<RouteHas>>,
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub missing: Option<Vec<RouteHas>>,
354}
355
356#[derive(
357    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
358)]
359#[serde(rename_all = "camelCase")]
360pub enum RedirectStatus {
361    StatusCode(f64),
362    Permanent(bool),
363}
364
365#[derive(
366    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
367)]
368#[serde(rename_all = "camelCase")]
369pub struct Redirect {
370    pub source: String,
371    pub destination: String,
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub base_path: Option<bool>,
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub locale: Option<bool>,
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub has: Option<Vec<RouteHas>>,
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub missing: Option<Vec<RouteHas>>,
380
381    #[serde(flatten)]
382    pub status: RedirectStatus,
383}
384
385#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
386#[serde(rename_all = "camelCase")]
387pub struct Rewrite {
388    pub source: String,
389    pub destination: String,
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub base_path: Option<bool>,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub locale: Option<bool>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub has: Option<Vec<RouteHas>>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub missing: Option<Vec<RouteHas>>,
398}
399
400#[turbo_tasks::value(eq = "manual")]
401#[derive(Clone, Debug, Default, PartialEq)]
402#[serde(rename_all = "camelCase")]
403pub struct Rewrites {
404    pub before_files: Vec<Rewrite>,
405    pub after_files: Vec<Rewrite>,
406    pub fallback: Vec<Rewrite>,
407}
408
409#[derive(
410    Clone,
411    Debug,
412    Default,
413    PartialEq,
414    Serialize,
415    Deserialize,
416    TraceRawVcs,
417    NonLocalValue,
418    OperationValue,
419)]
420#[serde(rename_all = "camelCase")]
421pub struct TypeScriptConfig {
422    pub ignore_build_errors: Option<bool>,
423    pub tsconfig_path: Option<String>,
424}
425
426#[turbo_tasks::value(eq = "manual", operation)]
427#[derive(Clone, Debug, PartialEq)]
428#[serde(rename_all = "camelCase")]
429pub struct ImageConfig {
430    pub device_sizes: Vec<u16>,
431    pub image_sizes: Vec<u16>,
432    pub path: String,
433    pub loader: ImageLoader,
434    #[serde(deserialize_with = "empty_string_is_none")]
435    pub loader_file: Option<String>,
436    pub domains: Vec<String>,
437    pub disable_static_images: bool,
438    #[serde(rename = "minimumCacheTTL")]
439    pub minimum_cache_ttl: u64,
440    pub formats: Vec<ImageFormat>,
441    #[serde(rename = "dangerouslyAllowSVG")]
442    pub dangerously_allow_svg: bool,
443    pub content_security_policy: String,
444    pub remote_patterns: Vec<RemotePattern>,
445    pub unoptimized: bool,
446}
447
448fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
449where
450    D: Deserializer<'de>,
451{
452    let o = Option::<String>::deserialize(deserializer)?;
453    Ok(o.filter(|s| !s.is_empty()))
454}
455
456impl Default for ImageConfig {
457    fn default() -> Self {
458        // https://github.com/vercel/next.js/blob/327634eb/packages/next/shared/lib/image-config.ts#L100-L114
459        Self {
460            device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
461            image_sizes: vec![16, 32, 48, 64, 96, 128, 256, 384],
462            path: "/_next/image".to_string(),
463            loader: ImageLoader::Default,
464            loader_file: None,
465            domains: vec![],
466            disable_static_images: false,
467            minimum_cache_ttl: 60,
468            formats: vec![ImageFormat::Webp],
469            dangerously_allow_svg: false,
470            content_security_policy: "".to_string(),
471            remote_patterns: vec![],
472            unoptimized: false,
473        }
474    }
475}
476
477#[derive(
478    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
479)]
480#[serde(rename_all = "kebab-case")]
481pub enum ImageLoader {
482    Default,
483    Imgix,
484    Cloudinary,
485    Akamai,
486    Custom,
487}
488
489#[derive(
490    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
491)]
492pub enum ImageFormat {
493    #[serde(rename = "image/webp")]
494    Webp,
495    #[serde(rename = "image/avif")]
496    Avif,
497}
498
499#[derive(
500    Clone,
501    Debug,
502    Default,
503    PartialEq,
504    Serialize,
505    Deserialize,
506    TraceRawVcs,
507    NonLocalValue,
508    OperationValue,
509)]
510#[serde(rename_all = "camelCase")]
511pub struct RemotePattern {
512    pub hostname: String,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub protocol: Option<RemotePatternProtocol>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub port: Option<String>,
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub pathname: Option<String>,
519}
520
521#[derive(
522    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
523)]
524#[serde(rename_all = "kebab-case")]
525pub enum RemotePatternProtocol {
526    Http,
527    Https,
528}
529
530#[derive(
531    Clone,
532    Debug,
533    Default,
534    PartialEq,
535    Serialize,
536    Deserialize,
537    TraceRawVcs,
538    NonLocalValue,
539    OperationValue,
540)]
541#[serde(rename_all = "camelCase")]
542pub struct TurbopackConfig {
543    /// This option has been replaced by `rules`.
544    pub loaders: Option<JsonValue>,
545    pub rules: Option<FxIndexMap<RcStr, RuleConfigItemOrShortcut>>,
546    #[turbo_tasks(trace_ignore)]
547    pub conditions: Option<FxIndexMap<RcStr, ConfigConditionItem>>,
548    pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
549    pub resolve_extensions: Option<Vec<RcStr>>,
550    pub module_ids: Option<ModuleIds>,
551}
552
553#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
554pub struct RegexComponents {
555    source: RcStr,
556    flags: RcStr,
557}
558
559#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
560#[serde(tag = "type", content = "value", rename_all = "camelCase")]
561pub enum ConfigConditionPath {
562    Glob(RcStr),
563    Regex(RegexComponents),
564}
565
566impl TryInto<ConditionPath> for ConfigConditionPath {
567    fn try_into(self) -> Result<ConditionPath> {
568        Ok(match self {
569            ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
570            ConfigConditionPath::Regex(path) => {
571                ConditionPath::Regex(EsRegex::new(&path.source, &path.flags)?.resolved_cell())
572            }
573        })
574    }
575
576    type Error = anyhow::Error;
577}
578
579#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
580pub struct ConfigConditionItem {
581    pub path: ConfigConditionPath,
582}
583
584impl TryInto<ConditionItem> for ConfigConditionItem {
585    fn try_into(self) -> Result<ConditionItem> {
586        Ok(ConditionItem {
587            path: self.path.try_into()?,
588        })
589    }
590
591    type Error = anyhow::Error;
592}
593
594#[derive(
595    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
596)]
597#[serde(rename_all = "camelCase")]
598pub struct RuleConfigItemOptions {
599    pub loaders: Vec<LoaderItem>,
600    #[serde(default, alias = "as")]
601    pub rename_as: Option<RcStr>,
602}
603
604#[derive(
605    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
606)]
607#[serde(rename_all = "camelCase", untagged)]
608pub enum RuleConfigItemOrShortcut {
609    Loaders(Vec<LoaderItem>),
610    Advanced(RuleConfigItem),
611}
612
613#[derive(
614    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
615)]
616#[serde(rename_all = "camelCase", untagged)]
617pub enum RuleConfigItem {
618    Options(RuleConfigItemOptions),
619    Conditional(FxIndexMap<RcStr, RuleConfigItem>),
620    Boolean(bool),
621}
622
623#[derive(
624    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
625)]
626#[serde(untagged)]
627pub enum LoaderItem {
628    LoaderName(RcStr),
629    LoaderOptions(WebpackLoaderItem),
630}
631
632#[turbo_tasks::value(operation)]
633#[derive(Copy, Clone, Debug)]
634#[serde(rename_all = "camelCase")]
635pub enum ModuleIds {
636    Named,
637    Deterministic,
638}
639
640#[turbo_tasks::value(transparent)]
641pub struct OptionModuleIds(pub Option<ModuleIds>);
642
643#[derive(
644    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
645)]
646#[serde(untagged)]
647pub enum MdxRsOptions {
648    Boolean(bool),
649    Option(MdxTransformOptions),
650}
651
652#[turbo_tasks::value(shared, operation)]
653#[derive(Clone, Debug)]
654#[serde(rename_all = "camelCase")]
655pub enum ReactCompilerMode {
656    Infer,
657    Annotation,
658    All,
659}
660
661/// Subset of react compiler options
662#[turbo_tasks::value(shared, operation)]
663#[derive(Clone, Debug)]
664#[serde(rename_all = "camelCase")]
665pub struct ReactCompilerOptions {
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub compilation_mode: Option<ReactCompilerMode>,
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub panic_threshold: Option<RcStr>,
670}
671
672#[derive(
673    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
674)]
675#[serde(untagged)]
676pub enum ReactCompilerOptionsOrBoolean {
677    Boolean(bool),
678    Option(ReactCompilerOptions),
679}
680
681#[turbo_tasks::value(transparent)]
682pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
683
684#[derive(
685    Clone,
686    Debug,
687    Default,
688    PartialEq,
689    Serialize,
690    Deserialize,
691    TraceRawVcs,
692    ValueDebugFormat,
693    NonLocalValue,
694    OperationValue,
695)]
696#[serde(rename_all = "camelCase")]
697pub struct ExperimentalConfig {
698    // all fields should be private and access should be wrapped within a turbo-tasks function
699    // Otherwise changing ExperimentalConfig will lead to invalidating all tasks accessing it.
700    allowed_revalidate_header_keys: Option<Vec<RcStr>>,
701    client_router_filter: Option<bool>,
702    /// decimal for percent for possible false positives e.g. 0.01 for 10%
703    /// potential false matches lower percent increases size of the filter
704    client_router_filter_allowed_rate: Option<f64>,
705    client_router_filter_redirects: Option<bool>,
706    fetch_cache_key_prefix: Option<RcStr>,
707    isr_flush_to_disk: Option<bool>,
708    /// For use with `@next/mdx`. Compile MDX files using the new Rust compiler.
709    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs)
710    mdx_rs: Option<MdxRsOptions>,
711    strict_next_head: Option<bool>,
712    swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
713    external_middleware_rewrites_resolve: Option<bool>,
714    scroll_restoration: Option<bool>,
715    manual_client_base_path: Option<bool>,
716    optimistic_client_cache: Option<bool>,
717    middleware_prefetch: Option<MiddlewarePrefetchType>,
718    /// optimizeCss can be boolean or critters' option object
719    /// Use Record<string, unknown> as critters doesn't export its Option type ([link](https://github.com/GoogleChromeLabs/critters/blob/a590c05f9197b656d2aeaae9369df2483c26b072/packages/critters/src/index.d.ts))
720    optimize_css: Option<serde_json::Value>,
721    next_script_workers: Option<bool>,
722    web_vitals_attribution: Option<Vec<RcStr>>,
723    server_actions: Option<ServerActionsOrLegacyBool>,
724    sri: Option<SubResourceIntegrity>,
725    react_compiler: Option<ReactCompilerOptionsOrBoolean>,
726    #[serde(rename = "dynamicIO")]
727    dynamic_io: Option<bool>,
728    use_cache: Option<bool>,
729    // ---
730    // UNSUPPORTED
731    // ---
732    adjust_font_fallbacks: Option<bool>,
733    adjust_font_fallbacks_with_size_adjust: Option<bool>,
734    after: Option<bool>,
735    amp: Option<serde_json::Value>,
736    app_document_preloading: Option<bool>,
737    cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
738    cache_life: Option<FxIndexMap<String, CacheLifeProfile>>,
739    case_sensitive_routes: Option<bool>,
740    cpus: Option<f64>,
741    cra_compat: Option<bool>,
742    disable_optimized_loading: Option<bool>,
743    disable_postcss_preset_env: Option<bool>,
744    esm_externals: Option<EsmExternals>,
745    extension_alias: Option<serde_json::Value>,
746    external_dir: Option<bool>,
747    /// If set to `false`, webpack won't fall back to polyfill Node.js modules
748    /// in the browser Full list of old polyfills is accessible here:
749    /// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
750    fallback_node_polyfills: Option<bool>, // false
751    force_swc_transforms: Option<bool>,
752    fully_specified: Option<bool>,
753    gzip_size: Option<bool>,
754
755    pub inline_css: Option<bool>,
756    instrumentation_hook: Option<bool>,
757    client_trace_metadata: Option<Vec<String>>,
758    large_page_data_bytes: Option<f64>,
759    logging: Option<serde_json::Value>,
760    memory_based_workers_count: Option<bool>,
761    /// Optimize React APIs for server builds.
762    optimize_server_react: Option<bool>,
763    /// Automatically apply the "modularize_imports" optimization to imports of
764    /// the specified packages.
765    optimize_package_imports: Option<Vec<RcStr>>,
766    /// Using this feature will enable the `react@experimental` for the `app`
767    /// directory.
768    ppr: Option<ExperimentalPartialPrerendering>,
769    taint: Option<bool>,
770    #[serde(rename = "routerBFCache")]
771    router_bfcache: Option<bool>,
772    proxy_timeout: Option<f64>,
773    /// enables the minification of server code.
774    server_minification: Option<bool>,
775    /// Enables source maps generation for the server production bundle.
776    server_source_maps: Option<bool>,
777    swc_trace_profiling: Option<bool>,
778    /// @internal Used by the Next.js internals only.
779    trust_host_header: Option<bool>,
780    /// Generate Route types and enable type checking for Link and Router.push,
781    /// etc. This option requires `appDir` to be enabled first.
782    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/typedRoutes)
783    typed_routes: Option<bool>,
784    url_imports: Option<serde_json::Value>,
785    view_transition: Option<bool>,
786    /// This option is to enable running the Webpack build in a worker thread
787    /// (doesn't apply to Turbopack).
788    webpack_build_worker: Option<bool>,
789    worker_threads: Option<bool>,
790
791    turbopack_minify: Option<bool>,
792    turbopack_persistent_caching: Option<bool>,
793    turbopack_source_maps: Option<bool>,
794    turbopack_tree_shaking: Option<bool>,
795    turbopack_scope_hoisting: Option<bool>,
796    // Whether to enable the global-not-found convention
797    global_not_found: Option<bool>,
798    /// Defaults to false in development mode, true in production mode.
799    turbopack_remove_unused_exports: Option<bool>,
800    /// Devtool option for the segment explorer.
801    devtool_segment_explorer: Option<bool>,
802}
803
804#[derive(
805    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
806)]
807#[serde(rename_all = "camelCase")]
808pub struct CacheLifeProfile {
809    #[serde(skip_serializing_if = "Option::is_none")]
810    pub stale: Option<u32>,
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub revalidate: Option<u32>,
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub expire: Option<u32>,
815}
816
817#[test]
818fn test_cache_life_profiles() {
819    let json = serde_json::json!({
820        "cacheLife": {
821            "frequent": {
822                "stale": 19,
823                "revalidate": 100,
824            },
825        }
826    });
827
828    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
829    let mut expected_cache_life = FxIndexMap::default();
830
831    expected_cache_life.insert(
832        "frequent".to_string(),
833        CacheLifeProfile {
834            stale: Some(19),
835            revalidate: Some(100),
836            expire: None,
837        },
838    );
839
840    assert_eq!(config.cache_life, Some(expected_cache_life));
841}
842
843#[test]
844fn test_cache_life_profiles_invalid() {
845    let json = serde_json::json!({
846        "cacheLife": {
847            "invalid": {
848                "stale": "invalid_value",
849            },
850        }
851    });
852
853    let result: Result<ExperimentalConfig, _> = serde_json::from_value(json);
854
855    assert!(
856        result.is_err(),
857        "Deserialization should fail due to invalid 'stale' value type"
858    );
859}
860
861#[derive(
862    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
863)]
864#[serde(rename_all = "lowercase")]
865pub enum ExperimentalPartialPrerenderingIncrementalValue {
866    Incremental,
867}
868
869#[derive(
870    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
871)]
872#[serde(untagged)]
873pub enum ExperimentalPartialPrerendering {
874    Boolean(bool),
875    Incremental(ExperimentalPartialPrerenderingIncrementalValue),
876}
877
878#[test]
879fn test_parse_experimental_partial_prerendering() {
880    let json = serde_json::json!({
881        "ppr": "incremental"
882    });
883    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
884    assert_eq!(
885        config.ppr,
886        Some(ExperimentalPartialPrerendering::Incremental(
887            ExperimentalPartialPrerenderingIncrementalValue::Incremental
888        ))
889    );
890
891    let json = serde_json::json!({
892        "ppr": true
893    });
894    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
895    assert_eq!(
896        config.ppr,
897        Some(ExperimentalPartialPrerendering::Boolean(true))
898    );
899
900    // Expect if we provide a random string, it will fail.
901    let json = serde_json::json!({
902        "ppr": "random"
903    });
904    let config = serde_json::from_value::<ExperimentalConfig>(json);
905    assert!(config.is_err());
906}
907
908#[derive(
909    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
910)]
911#[serde(rename_all = "camelCase")]
912pub struct SubResourceIntegrity {
913    pub algorithm: Option<RcStr>,
914}
915
916#[derive(
917    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
918)]
919#[serde(untagged)]
920pub enum ServerActionsOrLegacyBool {
921    /// The current way to configure server actions sub behaviors.
922    ServerActionsConfig(ServerActions),
923
924    /// The legacy way to disable server actions. This is no longer used, server
925    /// actions is always enabled.
926    LegacyBool(bool),
927}
928
929#[derive(
930    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
931)]
932#[serde(rename_all = "kebab-case")]
933pub enum EsmExternalsValue {
934    Loose,
935}
936
937#[derive(
938    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
939)]
940#[serde(untagged)]
941pub enum EsmExternals {
942    Loose(EsmExternalsValue),
943    Bool(bool),
944}
945
946// Test for esm externals deserialization.
947#[test]
948fn test_esm_externals_deserialization() {
949    let json = serde_json::json!({
950        "esmExternals": true
951    });
952    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
953    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
954
955    let json = serde_json::json!({
956        "esmExternals": "loose"
957    });
958    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
959    assert_eq!(
960        config.esm_externals,
961        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
962    );
963}
964
965#[derive(
966    Clone,
967    Debug,
968    Default,
969    PartialEq,
970    Eq,
971    Deserialize,
972    Serialize,
973    TraceRawVcs,
974    NonLocalValue,
975    OperationValue,
976)]
977#[serde(rename_all = "camelCase")]
978pub struct ServerActions {
979    /// Allows adjusting body parser size limit for server actions.
980    pub body_size_limit: Option<SizeLimit>,
981}
982
983#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue)]
984#[serde(untagged)]
985pub enum SizeLimit {
986    Number(f64),
987    WithUnit(String),
988}
989
990// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
991// implement Eq.
992impl PartialEq for SizeLimit {
993    fn eq(&self, other: &Self) -> bool {
994        match (self, other) {
995            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
996            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
997            _ => false,
998        }
999    }
1000}
1001
1002impl Eq for SizeLimit {}
1003
1004#[derive(
1005    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1006)]
1007#[serde(rename_all = "kebab-case")]
1008pub enum MiddlewarePrefetchType {
1009    Strict,
1010    Flexible,
1011}
1012
1013#[derive(
1014    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1015)]
1016#[serde(untagged)]
1017pub enum EmotionTransformOptionsOrBoolean {
1018    Boolean(bool),
1019    Options(EmotionTransformConfig),
1020}
1021
1022impl EmotionTransformOptionsOrBoolean {
1023    pub fn is_enabled(&self) -> bool {
1024        match self {
1025            Self::Boolean(enabled) => *enabled,
1026            _ => true,
1027        }
1028    }
1029}
1030
1031#[derive(
1032    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1033)]
1034#[serde(untagged)]
1035pub enum StyledComponentsTransformOptionsOrBoolean {
1036    Boolean(bool),
1037    Options(StyledComponentsTransformConfig),
1038}
1039
1040impl StyledComponentsTransformOptionsOrBoolean {
1041    pub fn is_enabled(&self) -> bool {
1042        match self {
1043            Self::Boolean(enabled) => *enabled,
1044            _ => true,
1045        }
1046    }
1047}
1048
1049#[turbo_tasks::value(eq = "manual")]
1050#[derive(Clone, Debug, PartialEq, Default, OperationValue)]
1051#[serde(rename_all = "camelCase")]
1052pub struct CompilerConfig {
1053    pub react_remove_properties: Option<ReactRemoveProperties>,
1054    pub relay: Option<RelayConfig>,
1055    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1056    pub remove_console: Option<RemoveConsoleConfig>,
1057    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1058}
1059
1060#[derive(
1061    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1062)]
1063#[serde(untagged, rename_all = "camelCase")]
1064pub enum ReactRemoveProperties {
1065    Boolean(bool),
1066    Config { properties: Option<Vec<String>> },
1067}
1068
1069impl ReactRemoveProperties {
1070    pub fn is_enabled(&self) -> bool {
1071        match self {
1072            Self::Boolean(enabled) => *enabled,
1073            _ => true,
1074        }
1075    }
1076}
1077
1078#[derive(
1079    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1080)]
1081#[serde(untagged)]
1082pub enum RemoveConsoleConfig {
1083    Boolean(bool),
1084    Config { exclude: Option<Vec<String>> },
1085}
1086
1087impl RemoveConsoleConfig {
1088    pub fn is_enabled(&self) -> bool {
1089        match self {
1090            Self::Boolean(enabled) => *enabled,
1091            _ => true,
1092        }
1093    }
1094}
1095
1096#[turbo_tasks::value(transparent)]
1097pub struct ResolveExtensions(Option<Vec<RcStr>>);
1098
1099#[turbo_tasks::value(transparent)]
1100pub struct SwcPlugins(Vec<(RcStr, serde_json::Value)>);
1101
1102#[turbo_tasks::value(transparent)]
1103pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1104
1105#[turbo_tasks::value(transparent)]
1106
1107pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1108
1109#[turbo_tasks::value(transparent)]
1110pub struct OptionServerActions(Option<ServerActions>);
1111
1112#[turbo_tasks::value(transparent)]
1113pub struct OptionJsonValue(pub Option<serde_json::Value>);
1114
1115#[turbo_tasks::value_impl]
1116impl NextConfig {
1117    #[turbo_tasks::function]
1118    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1119        let string = string.await?;
1120        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1121        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1122            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1123        Ok(config.cell())
1124    }
1125
1126    #[turbo_tasks::function]
1127    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1128        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1129    }
1130
1131    #[turbo_tasks::function]
1132    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1133        Vc::cell(self.react_production_profiling.unwrap_or_default())
1134    }
1135
1136    #[turbo_tasks::function]
1137    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1138        Vc::cell(
1139            self.server_external_packages
1140                .as_ref()
1141                .cloned()
1142                .unwrap_or_default(),
1143        )
1144    }
1145
1146    #[turbo_tasks::function]
1147    pub fn is_standalone(&self) -> Vc<bool> {
1148        Vc::cell(self.output == Some(OutputType::Standalone))
1149    }
1150
1151    #[turbo_tasks::function]
1152    pub fn cache_handler(&self) -> Vc<Option<RcStr>> {
1153        Vc::cell(self.cache_handler.clone())
1154    }
1155
1156    #[turbo_tasks::function]
1157    pub fn compiler(&self) -> Vc<CompilerConfig> {
1158        self.compiler.clone().unwrap_or_default().cell()
1159    }
1160
1161    #[turbo_tasks::function]
1162    pub fn env(&self) -> Vc<EnvMap> {
1163        // The value expected for env is Record<String, String>, but config itself
1164        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1165        // then stringifies it. We do the interop here as well.
1166        let env = self
1167            .env
1168            .iter()
1169            .map(|(k, v)| {
1170                (
1171                    k.as_str().into(),
1172                    if let JsonValue::String(s) = v {
1173                        // A string value is kept, calling `to_string` would wrap in to quotes.
1174                        s.as_str().into()
1175                    } else {
1176                        v.to_string().into()
1177                    },
1178                )
1179            })
1180            .collect();
1181
1182        Vc::cell(env)
1183    }
1184
1185    #[turbo_tasks::function]
1186    pub fn image_config(&self) -> Vc<ImageConfig> {
1187        self.images.clone().cell()
1188    }
1189
1190    #[turbo_tasks::function]
1191    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1192        Vc::cell(self.page_extensions.clone())
1193    }
1194
1195    #[turbo_tasks::function]
1196    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1197        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1198    }
1199
1200    #[turbo_tasks::function]
1201    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1202        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1203    }
1204
1205    #[turbo_tasks::function]
1206    pub fn webpack_rules(&self, active_conditions: Vec<RcStr>) -> Vc<OptionWebpackRules> {
1207        let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1208            return Vc::cell(None);
1209        };
1210        if turbo_rules.is_empty() {
1211            return Vc::cell(None);
1212        }
1213        let active_conditions = active_conditions.into_iter().collect::<FxHashSet<_>>();
1214        let mut rules = FxIndexMap::default();
1215        for (ext, rule) in turbo_rules.iter() {
1216            fn transform_loaders(loaders: &[LoaderItem]) -> ResolvedVc<WebpackLoaderItems> {
1217                ResolvedVc::cell(
1218                    loaders
1219                        .iter()
1220                        .map(|item| match item {
1221                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1222                                loader: name.clone(),
1223                                options: Default::default(),
1224                            },
1225                            LoaderItem::LoaderOptions(options) => options.clone(),
1226                        })
1227                        .collect(),
1228                )
1229            }
1230            enum FindRuleResult<'a> {
1231                Found(&'a RuleConfigItemOptions),
1232                NotFound,
1233                Break,
1234            }
1235            fn find_rule<'a>(
1236                rule: &'a RuleConfigItem,
1237                active_conditions: &FxHashSet<RcStr>,
1238            ) -> FindRuleResult<'a> {
1239                match rule {
1240                    RuleConfigItem::Options(rule) => FindRuleResult::Found(rule),
1241                    RuleConfigItem::Conditional(map) => {
1242                        for (condition, rule) in map.iter() {
1243                            if condition == "default" || active_conditions.contains(condition) {
1244                                match find_rule(rule, active_conditions) {
1245                                    FindRuleResult::Found(rule) => {
1246                                        return FindRuleResult::Found(rule);
1247                                    }
1248                                    FindRuleResult::Break => {
1249                                        return FindRuleResult::Break;
1250                                    }
1251                                    FindRuleResult::NotFound => {}
1252                                }
1253                            }
1254                        }
1255                        FindRuleResult::NotFound
1256                    }
1257                    RuleConfigItem::Boolean(_) => FindRuleResult::Break,
1258                }
1259            }
1260            match rule {
1261                RuleConfigItemOrShortcut::Loaders(loaders) => {
1262                    rules.insert(
1263                        ext.clone(),
1264                        LoaderRuleItem {
1265                            loaders: transform_loaders(loaders),
1266                            rename_as: None,
1267                        },
1268                    );
1269                }
1270                RuleConfigItemOrShortcut::Advanced(rule) => {
1271                    if let FindRuleResult::Found(RuleConfigItemOptions { loaders, rename_as }) =
1272                        find_rule(rule, &active_conditions)
1273                    {
1274                        rules.insert(
1275                            ext.clone(),
1276                            LoaderRuleItem {
1277                                loaders: transform_loaders(loaders),
1278                                rename_as: rename_as.clone(),
1279                            },
1280                        );
1281                    }
1282                }
1283            }
1284        }
1285        Vc::cell(Some(ResolvedVc::cell(rules)))
1286    }
1287
1288    #[turbo_tasks::function]
1289    pub fn webpack_conditions(&self) -> Result<Vc<OptionWebpackConditions>> {
1290        let Some(config_conditions) = self.turbopack.as_ref().and_then(|t| t.conditions.as_ref())
1291        else {
1292            return Ok(Vc::cell(None));
1293        };
1294
1295        let conditions = config_conditions
1296            .iter()
1297            .map(|(k, v)| {
1298                let item: Result<ConditionItem> = TryInto::<ConditionItem>::try_into((*v).clone());
1299                item.map(|item| (k.clone(), item))
1300            })
1301            .collect::<Result<FxIndexMap<RcStr, ConditionItem>>>()?;
1302
1303        Ok(Vc::cell(Some(ResolvedVc::cell(conditions))))
1304    }
1305
1306    #[turbo_tasks::function]
1307    pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1308        Ok(Vc::cell(
1309            self.experimental
1310                .turbopack_persistent_caching
1311                .unwrap_or_default(),
1312        ))
1313    }
1314
1315    #[turbo_tasks::function]
1316    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1317        let Some(resolve_alias) = self
1318            .turbopack
1319            .as_ref()
1320            .and_then(|t| t.resolve_alias.as_ref())
1321        else {
1322            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1323        };
1324        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1325        Ok(alias_map.cell())
1326    }
1327
1328    #[turbo_tasks::function]
1329    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1330        let Some(resolve_extensions) = self
1331            .turbopack
1332            .as_ref()
1333            .and_then(|t| t.resolve_extensions.as_ref())
1334        else {
1335            return Vc::cell(None);
1336        };
1337        Vc::cell(Some(resolve_extensions.clone()))
1338    }
1339
1340    #[turbo_tasks::function]
1341    pub fn import_externals(&self) -> Result<Vc<bool>> {
1342        Ok(Vc::cell(match self.experimental.esm_externals {
1343            Some(EsmExternals::Bool(b)) => b,
1344            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1345            None => true,
1346        }))
1347    }
1348
1349    #[turbo_tasks::function]
1350    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1351        let options = &self.experimental.mdx_rs;
1352
1353        let options = match options {
1354            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1355                MdxTransformOptions {
1356                    provider_import_source: Some(mdx_import_source_file()),
1357                    ..Default::default()
1358                }
1359                .resolved_cell(),
1360            )),
1361            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1362                MdxTransformOptions {
1363                    provider_import_source: Some(
1364                        options
1365                            .provider_import_source
1366                            .clone()
1367                            .unwrap_or(mdx_import_source_file()),
1368                    ),
1369                    ..options.clone()
1370                }
1371                .resolved_cell(),
1372            )),
1373            _ => OptionalMdxTransformOptions(None),
1374        };
1375
1376        options.cell()
1377    }
1378
1379    #[turbo_tasks::function]
1380    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1381        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1382    }
1383
1384    #[turbo_tasks::function]
1385    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1386        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1387    }
1388
1389    #[turbo_tasks::function]
1390    pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1391        Vc::cell(self.experimental.sri.clone())
1392    }
1393
1394    #[turbo_tasks::function]
1395    pub fn experimental_server_actions(&self) -> Vc<OptionServerActions> {
1396        Vc::cell(match self.experimental.server_actions.as_ref() {
1397            Some(ServerActionsOrLegacyBool::ServerActionsConfig(server_actions)) => {
1398                Some(server_actions.clone())
1399            }
1400            Some(ServerActionsOrLegacyBool::LegacyBool(true)) => Some(ServerActions::default()),
1401            _ => None,
1402        })
1403    }
1404
1405    #[turbo_tasks::function]
1406    pub fn react_compiler(&self) -> Vc<OptionalReactCompilerOptions> {
1407        let options = &self.experimental.react_compiler;
1408
1409        let options = match options {
1410            Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1411                OptionalReactCompilerOptions(Some(
1412                    ReactCompilerOptions {
1413                        compilation_mode: None,
1414                        panic_threshold: None,
1415                    }
1416                    .resolved_cell(),
1417                ))
1418            }
1419            Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1420                Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1421            ),
1422            _ => OptionalReactCompilerOptions(None),
1423        };
1424
1425        options.cell()
1426    }
1427
1428    #[turbo_tasks::function]
1429    pub fn sass_config(&self) -> Vc<JsonValue> {
1430        Vc::cell(self.sass_options.clone().unwrap_or_default())
1431    }
1432
1433    #[turbo_tasks::function]
1434    pub fn skip_middleware_url_normalize(&self) -> Vc<bool> {
1435        Vc::cell(self.skip_middleware_url_normalize.unwrap_or(false))
1436    }
1437
1438    #[turbo_tasks::function]
1439    pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1440        Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1441    }
1442
1443    /// Returns the final asset prefix. If an assetPrefix is set, it's used.
1444    /// Otherwise, the basePath is used.
1445    #[turbo_tasks::function]
1446    pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1447        let this = self.await?;
1448
1449        Ok(Vc::cell(Some(
1450            format!(
1451                "{}/_next/",
1452                if let Some(asset_prefix) = &this.asset_prefix {
1453                    asset_prefix
1454                } else {
1455                    this.base_path.as_ref().map_or("", |b| b.as_str())
1456                }
1457                .trim_end_matches('/')
1458            )
1459            .into(),
1460        )))
1461    }
1462
1463    /// Returns the suffix to use for chunk loading.
1464    #[turbo_tasks::function]
1465    pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1466        let this = self.await?;
1467
1468        match &this.deployment_id {
1469            Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1470            None => Ok(Vc::cell(None)),
1471        }
1472    }
1473
1474    #[turbo_tasks::function]
1475    pub fn enable_ppr(&self) -> Vc<bool> {
1476        Vc::cell(
1477            self.experimental
1478                .ppr
1479                .as_ref()
1480                .map(|ppr| match ppr {
1481                    ExperimentalPartialPrerendering::Incremental(
1482                        ExperimentalPartialPrerenderingIncrementalValue::Incremental,
1483                    ) => true,
1484                    ExperimentalPartialPrerendering::Boolean(b) => *b,
1485                })
1486                .unwrap_or(false),
1487        )
1488    }
1489
1490    #[turbo_tasks::function]
1491    pub fn enable_taint(&self) -> Vc<bool> {
1492        Vc::cell(self.experimental.taint.unwrap_or(false))
1493    }
1494
1495    #[turbo_tasks::function]
1496    pub fn enable_router_bfcache(&self) -> Vc<bool> {
1497        Vc::cell(self.experimental.router_bfcache.unwrap_or(false))
1498    }
1499
1500    #[turbo_tasks::function]
1501    pub fn enable_view_transition(&self) -> Vc<bool> {
1502        Vc::cell(self.experimental.view_transition.unwrap_or(false))
1503    }
1504
1505    #[turbo_tasks::function]
1506    pub fn enable_dynamic_io(&self) -> Vc<bool> {
1507        Vc::cell(self.experimental.dynamic_io.unwrap_or(false))
1508    }
1509
1510    #[turbo_tasks::function]
1511    pub fn enable_use_cache(&self) -> Vc<bool> {
1512        Vc::cell(
1513            self.experimental
1514                .use_cache
1515                // "use cache" was originally implicitly enabled with the
1516                // dynamicIO flag, so we transfer the value for dynamicIO to the
1517                // explicit useCache flag to ensure backwards compatibility.
1518                .unwrap_or(self.experimental.dynamic_io.unwrap_or(false)),
1519        )
1520    }
1521
1522    #[turbo_tasks::function]
1523    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1524        let mut cache_kinds = CacheKinds::default();
1525
1526        if let Some(handlers) = self.experimental.cache_handlers.as_ref() {
1527            cache_kinds.extend(handlers.keys().cloned());
1528        }
1529
1530        cache_kinds.cell()
1531    }
1532
1533    #[turbo_tasks::function]
1534    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1535        Vc::cell(
1536            self.experimental
1537                .optimize_package_imports
1538                .clone()
1539                .unwrap_or_default(),
1540        )
1541    }
1542
1543    #[turbo_tasks::function]
1544    pub fn tree_shaking_mode_for_foreign_code(
1545        &self,
1546        _is_development: bool,
1547    ) -> Vc<OptionTreeShaking> {
1548        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1549            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1550            Some(true) => Some(TreeShakingMode::ModuleFragments),
1551            None => Some(TreeShakingMode::ReexportsOnly),
1552        })
1553        .cell()
1554    }
1555
1556    #[turbo_tasks::function]
1557    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1558        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1559            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1560            Some(true) => Some(TreeShakingMode::ModuleFragments),
1561            None => Some(TreeShakingMode::ReexportsOnly),
1562        })
1563        .cell()
1564    }
1565
1566    #[turbo_tasks::function]
1567    pub fn turbopack_remove_unused_exports(&self, is_development: bool) -> Vc<bool> {
1568        Vc::cell(
1569            self.experimental
1570                .turbopack_remove_unused_exports
1571                .unwrap_or(!is_development),
1572        )
1573    }
1574
1575    #[turbo_tasks::function]
1576    pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1577        Ok(match *mode.await? {
1578            // Ignore configuration in development mode, HMR only works with `named`
1579            NextMode::Development => ModuleIds::Named.cell(),
1580            NextMode::Build => self
1581                .turbopack
1582                .as_ref()
1583                .and_then(|t| t.module_ids)
1584                .unwrap_or(ModuleIds::Deterministic)
1585                .cell(),
1586        })
1587    }
1588
1589    #[turbo_tasks::function]
1590    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1591        let minify = self.experimental.turbopack_minify;
1592        Ok(Vc::cell(
1593            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1594        ))
1595    }
1596
1597    #[turbo_tasks::function]
1598    pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1599        Ok(Vc::cell(match *mode.await? {
1600            // Ignore configuration in development mode to not break HMR
1601            NextMode::Development => false,
1602            NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1603        }))
1604    }
1605
1606    #[turbo_tasks::function]
1607    pub fn client_source_maps(&self, _mode: Vc<NextMode>) -> Result<Vc<bool>> {
1608        // Temporarily always enable client source maps as tests regress.
1609        // TODO: Respect both `self.experimental.turbopack_source_maps` and
1610        //       `self.production_browser_source_maps`
1611        let source_maps = self.experimental.turbopack_source_maps;
1612        Ok(Vc::cell(source_maps.unwrap_or(true)))
1613    }
1614
1615    #[turbo_tasks::function]
1616    pub fn server_source_maps(&self) -> Result<Vc<bool>> {
1617        let source_maps = self.experimental.turbopack_source_maps;
1618        Ok(Vc::cell(source_maps.unwrap_or(true)))
1619    }
1620
1621    #[turbo_tasks::function]
1622    pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
1623        Ok(Vc::cell(
1624            self.typescript
1625                .tsconfig_path
1626                .as_ref()
1627                .map(|path| path.to_owned().into()),
1628        ))
1629    }
1630
1631    #[turbo_tasks::function]
1632    pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
1633        Vc::cell(self.output_file_tracing_includes.clone())
1634    }
1635
1636    #[turbo_tasks::function]
1637    pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
1638        Vc::cell(self.output_file_tracing_excludes.clone())
1639    }
1640}
1641
1642/// A subset of ts/jsconfig that next.js implicitly
1643/// interops with.
1644#[turbo_tasks::value(serialization = "custom", eq = "manual")]
1645#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1646#[serde(rename_all = "camelCase")]
1647pub struct JsConfig {
1648    compiler_options: Option<serde_json::Value>,
1649}
1650
1651#[turbo_tasks::value_impl]
1652impl JsConfig {
1653    #[turbo_tasks::function]
1654    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1655        let string = string.await?;
1656        let config: JsConfig = serde_json::from_str(&string)
1657            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1658
1659        Ok(config.cell())
1660    }
1661
1662    #[turbo_tasks::function]
1663    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
1664        Vc::cell(self.compiler_options.clone().unwrap_or_default())
1665    }
1666}