Repository: vercel/next.js
Pull request #79451: Turbopack: avoid negative uppers and follower and use retry loop instead
Author: login: sokra
Labels: name: Turbopack
description: Related to Turbopack with Next.js.
name: created-by: Turbopack team
description: PRs by the Turbopack team.
name: locked
Created: 2025-05-21T12:50:50Z | Merged: 2025-05-21T22:39:25Z

### Why?

Multiple graph modifications can happen concurrently. Each modification only locks one or two tasks in the graph at a single step, but a full graph modification could affect many tasks in graph. So modifications are executed step by step only affecting a maximum of two tasks on each step.

Because of this operation mode modifications bubble through the graph and multiple modifications can bubble through the graph concurrently, even at different speeds.

One important case is that an "add" modification could be partially applied, while a "remove" modification for the same item is triggered. This "remove" modification could be faster and would try to remove items from tasks where they have not been added yet (as the "add" modification is still in progress).

To account for this problem we used to allow negative counts on each item, so the "remove" modification would temporarily cause negative item counts until the "add" modification returns the count to zero.

But some decisions (e. g. if a upper or follower should be removed) are based on the values of these counts, so this approach doesn't work correctly in some rare race condition edge cases. So this change uses a different approach.

### What?

We want to avoid negative counts, so when the "remove" modification tries to remove an non-existent item, it should wait for the "add" modification to catch up before continuing. There are two cases here: Either the "add" modification is done by a different thread, or it's done by the same thread and is in the queue. The first case is handled by a busy loop yielding. The second case is handled by re-enqueuing the job at the end of the queue.

There is a limit of 10s for retries after that we panic. This allows to detect invalid graph scenarios and points out bugs in the graph implementation. Let's hope we never see this panic. In the old implementation this problem was silently ignored: Count got negative, but that was just accepted and has let to weird side effects (like errors/collectibles not disappearing or hanging compilation).

Closes PACK-4648


Diff:
diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
index 46d70c7e9c56..56619cee3d99 100644
--- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
+++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
@@ -4,8 +4,12 @@ use std::{
     hash::Hash,
     mem::take,
     num::NonZeroU32,
+    ops::ControlFlow,
+    thread::yield_now,
+    time::{Duration, Instant},
 };
 
+use anyhow::Result;
 use indexmap::map::Entry;
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq};
@@ -151,6 +155,7 @@ pub enum AggregationUpdateJob {
     InnerOfUppersLostFollower {
         upper_ids: TaskIdVec,
         lost_follower_id: TaskId,
+        retry: u16,
     },
     /// Notifies multiple upper tasks that one of its inner tasks has lost followers.
     InnerOfUppersLostFollowers(Box<InnerOfUppersLostFollowersJob>),
@@ -158,6 +163,7 @@ pub enum AggregationUpdateJob {
     InnerOfUpperLostFollowers {
         upper_id: TaskId,
         lost_follower_ids: TaskIdVec,
+        retry: u16,
     },
     /// Notifies an upper task about changed data from an inner task.
     AggregatedDataUpdate(Box<AggregatedDataUpdateJob>),
@@ -831,7 +837,7 @@ impl AggregationUpdateQueue {
                             } else {
                                 take(upper_ids)
                             };
-                            self.inner_of_uppers_lost_follower(ctx, lost_follower_id, upper_ids);
+                            self.inner_of_uppers_lost_follower(ctx, lost_follower_id, upper_ids, 0);
                         }
                     } else if let Some(upper_id) = upper_ids.pop() {
                         let lost_follower_ids = if !upper_ids.is_empty() {
@@ -843,20 +849,22 @@ impl AggregationUpdateQueue {
                         } else {
                             take(lost_follower_ids)
                         };
-                        self.inner_of_upper_lost_followers(ctx, lost_follower_ids, upper_id);
+                        self.inner_of_upper_lost_followers(ctx, lost_follower_ids, upper_id, 0);
                     }
                 }
                 AggregationUpdateJob::InnerOfUppersLostFollower {
                     upper_ids,
                     lost_follower_id,
+                    retry,
                 } => {
-                    self.inner_of_uppers_lost_follower(ctx, lost_follower_id, upper_ids);
+                    self.inner_of_uppers_lost_follower(ctx, lost_follower_id, upper_ids, retry);
                 }
                 AggregationUpdateJob::InnerOfUpperLostFollowers {
                     upper_id,
                     lost_follower_ids,
+                    retry,
                 } => {
-                    self.inner_of_upper_lost_followers(ctx, lost_follower_ids, upper_id);
+                    self.inner_of_upper_lost_followers(ctx, lost_follower_ids, upper_id, retry);
                 }
                 AggregationUpdateJob::AggregatedDataUpdate(box AggregatedDataUpdateJob {
                     upper_ids,
@@ -1080,6 +1088,7 @@ impl AggregationUpdateQueue {
                         self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
                             upper_ids,
                             lost_follower_id: task_id,
+                            retry: 0,
                         });
                     }
 
@@ -1273,119 +1282,163 @@ impl AggregationUpdateQueue {
         ctx: &mut impl ExecuteContext,
         lost_follower_id: TaskId,
         mut upper_ids: TaskIdVec,
+        mut retry: u16,
     ) {
         #[cfg(feature = "trace_aggregation_update")]
         let _span = trace_span!("lost follower (n uppers)", uppers = upper_ids.len()).entered();
 
-        let mut follower = ctx.task(
-            lost_follower_id,
-            // For performance reasons this should stay `Meta` and not `All`
-            TaskDataCategory::Meta,
-        );
-        let mut follower_in_upper_ids = Vec::new();
-        let mut persistent_uppers = 0;
-        upper_ids.retain(|&mut upper_id| {
-            let mut keep_upper = false;
-            update!(follower, Upper { task: upper_id }, |old| {
-                let Some(old) = old else {
-                    follower_in_upper_ids.push(upper_id);
-                    return None;
-                };
-                if old < 0 {
-                    follower_in_upper_ids.push(upper_id);
-                    return Some(old);
-                }
-                if old == 1 {
-                    keep_upper = true;
-                    if !upper_id.is_transient() {
-                        persistent_uppers += 1;
+        // see documentation of `retry_loop` for more information why this is needed
+        let result = retry_loop(|| {
+            let mut follower = ctx.task(
+                lost_follower_id,
+                // For performance reasons this should stay `Meta` and not `All`
+                TaskDataCategory::Meta,
+            );
+            let mut removed_uppers = SmallVec::new();
+            swap_retain(&mut upper_ids, |&mut upper_id| {
+                let mut keep_upper = false;
+                let mut follower_in_upper = false;
+
+                update!(follower, Upper { task: upper_id }, |old| {
+                    let Some(old) = old else {
+                        follower_in_upper = true;
+                        return None;
+                    };
+                    if old < 0 {
+                        follower_in_upper = true;
+                        return Some(old);
                     }
-                    return None;
-                }
-                Some(old - 1)
+                    if old == 1 {
+                        keep_upper = true;
+                        removed_uppers.push(upper_id);
+                        return None;
+                    }
+                    Some(old - 1)
+                });
+                follower_in_upper
             });
-            keep_upper
-        });
-        if !upper_ids.is_empty() {
-            let data = AggregatedDataUpdate::from_task(&mut follower).invert();
-            let followers = get_followers(&follower);
-            drop(follower);
+            if !removed_uppers.is_empty() {
+                let data = AggregatedDataUpdate::from_task(&mut follower).invert();
+                let followers = get_followers(&follower);
+                drop(follower);
 
-            if !data.is_empty() {
-                for upper_id in upper_ids.iter() {
-                    // remove data from upper
-                    let mut upper = ctx.task(
-                        *upper_id,
-                        // For performance reasons this should stay `Meta` and not `All`
-                        TaskDataCategory::Meta,
-                    );
-                    let diff = data.apply(
-                        &mut upper,
-                        ctx.session_id(),
-                        ctx.should_track_activeness(),
-                        self,
-                    );
-                    if !diff.is_empty() {
-                        let upper_ids = get_uppers(&upper);
-                        self.push(
-                            AggregatedDataUpdateJob {
-                                upper_ids,
-                                update: diff,
-                            }
-                            .into(),
-                        )
+                if !data.is_empty() {
+                    for upper_id in removed_uppers.iter() {
+                        // remove data from upper
+                        let mut upper = ctx.task(
+                            *upper_id,
+                            // For performance reasons this should stay `Meta` and not `All`
+                            TaskDataCategory::Meta,
+                        );
+                        let diff = data.apply(
+                            &mut upper,
+                            ctx.session_id(),
+                            ctx.should_track_activeness(),
+                            self,
+                        );
+                        if !diff.is_empty() {
+                            let upper_ids = get_uppers(&upper);
+                            self.push(
+                                AggregatedDataUpdateJob {
+                                    upper_ids,
+                                    update: diff,
+                                }
+                                .into(),
+                            )
+                        }
                     }
                 }
+                if !followers.is_empty() {
+                    self.push(
+                        InnerOfUppersLostFollowersJob {
+                            upper_ids: removed_uppers.clone(),
+                            lost_follower_ids: followers,
+                        }
+                        .into(),
+                    );
+                }
+            } else {
+                drop(follower);
             }
-            if !followers.is_empty() {
-                self.push(
-                    InnerOfUppersLostFollowersJob {
-                        upper_ids: upper_ids.clone(),
-                        lost_follower_ids: followers,
+
+            if upper_ids.is_empty() {
+                return ControlFlow::Break(());
+            }
+            swap_retain(&mut upper_ids, |&mut upper_id| {
+                let mut upper = ctx.task(
+                    upper_id,
+                    // For performance reasons this should stay `Meta` and not `All`
+                    TaskDataCategory::Meta,
+                );
+                let mut inner_in_upper = false;
+                let mut removed_follower = false;
+                update!(
+                    upper,
+                    Follower {
+                        task: lost_follower_id
+                    },
+                    |old| {
+                        let Some(old) = old else {
+                            inner_in_upper = true;
+                            return None;
+                        };
+                        if old < 0 {
+                            inner_in_upper = true;
+                            return Some(old);
+                        }
+                        if old == 1 {
+                            removed_follower = true;
+                            return None;
+                        }
+                        Some(old - 1)
                     }
-                    .into(),
                 );
-            }
-        } else {
-            drop(follower);
-        }
-
-        for upper_id in follower_in_upper_ids {
-            let mut upper = ctx.task(
-                upper_id,
-                // For performance reasons this should stay `Meta` and not `All`
-                TaskDataCategory::Meta,
-            );
-            if update_count!(
-                upper,
-                Follower {
-                    task: lost_follower_id
-                },
-                -1
-            ) {
-                // May optimize the task
-                if count!(upper, Follower).is_power_of_two() {
-                    self.push_optimize_task(upper_id);
-                }
+                if removed_follower {
+                    // May optimize the task
+                    if count!(upper, Follower).is_power_of_two() {
+                        self.push_optimize_task(upper_id);
+                    }
 
-                let has_active_count = ctx.should_track_activeness()
-                    && get!(upper, Activeness).is_some_and(|a| a.active_counter > 0);
-                let upper_ids = get_uppers(&upper);
-                drop(upper);
-                // update active count
-                if has_active_count {
-                    self.push(AggregationUpdateJob::DecreaseActiveCount {
-                        task: lost_follower_id,
-                    });
-                }
-                // notify uppers about new follower
-                if !upper_ids.is_empty() {
-                    self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
-                        upper_ids,
-                        lost_follower_id,
-                    });
+                    let has_active_count = ctx.should_track_activeness()
+                        && get!(upper, Activeness).is_some_and(|a| a.active_counter > 0);
+                    let upper_ids = get_uppers(&upper);
+                    drop(upper);
+                    // update active count
+                    if has_active_count {
+                        self.push(AggregationUpdateJob::DecreaseActiveCount {
+                            task: lost_follower_id,
+                        });
+                    }
+                    // notify uppers about new follower
+                    if !upper_ids.is_empty() {
+                        self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
+                            upper_ids,
+                            lost_follower_id,
+                            retry: 0,
+                        });
+                    }
                 }
+                inner_in_upper
+            });
+            if upper_ids.is_empty() {
+                return ControlFlow::Break(());
+            }
+            ControlFlow::Continue(())
+        });
+        if result.is_err() {
+            retry += 1;
+            if retry > MAX_RETRIES {
+                panic!(
+                    "inner_of_uppers_lost_follower is not able to remove follower \
+                     {lost_follower_id:?} from {upper_ids:?} as they don't exist as upper or \
+                     follower edges"
+                );
             }
+            self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
+                upper_ids,
+                lost_follower_id,
+                retry,
+            });
         }
     }
 
@@ -1394,6 +1447,7 @@ impl AggregationUpdateQueue {
         ctx: &mut impl ExecuteContext,
         mut lost_follower_ids: TaskIdVec,
         upper_id: TaskId,
+        mut retry: u16,
     ) {
         #[cfg(feature = "trace_aggregation_update")]
         let _span = trace_span!(
@@ -1402,105 +1456,150 @@ impl AggregationUpdateQueue {
         )
         .entered();
 
-        lost_follower_ids.retain(|lost_follower_id| {
-            let mut follower = ctx.task(
-                *lost_follower_id,
-                // For performance reasons this should stay `Meta` and not `All`
-                TaskDataCategory::Meta,
-            );
-            let mut remove_upper = false;
-            let mut follower_in_upper = false;
-            update!(follower, Upper { task: upper_id }, |old| {
-                let Some(old) = old else {
-                    follower_in_upper = true;
-                    return None;
-                };
-                if old < 0 {
-                    follower_in_upper = true;
-                    return Some(old);
-                }
-                if old == 1 {
-                    remove_upper = true;
-                    return None;
+        // see documentation of `retry_loop` for more information why this is needed
+        let result = retry_loop(|| {
+            swap_retain(&mut lost_follower_ids, |&mut lost_follower_id| {
+                let mut follower = ctx.task(
+                    lost_follower_id,
+                    // For performance reasons this should stay `Meta` and not `All`
+                    TaskDataCategory::Meta,
+                );
+                let mut remove_upper = false;
+                let mut follower_in_upper = false;
+                update!(follower, Upper { task: upper_id }, |old| {
+                    let Some(old) = old else {
+                        follower_in_upper = true;
+                        return None;
+                    };
+                    if old < 0 {
+                        follower_in_upper = true;
+                        return Some(old);
+                    }
+                    if old == 1 {
+                        remove_upper = true;
+                        return None;
+                    }
+                    Some(old - 1)
+                });
+                if remove_upper {
+                    let data = AggregatedDataUpdate::from_task(&mut follower).invert();
+                    let followers = get_followers(&follower);
+                    drop(follower);
+
+                    if !data.is_empty() {
+                        // remove data from upper
+                        let mut upper = ctx.task(
+                            upper_id,
+                            // For performance reasons this should stay `Meta` and not `All`
+                            TaskDataCategory::Meta,
+                        );
+                        let diff = data.apply(
+                            &mut upper,
+                            ctx.session_id(),
+                            ctx.should_track_activeness(),
+                            self,
+                        );
+                        if !diff.is_empty() {
+                            let upper_ids = get_uppers(&upper);
+                            self.push(
+                                AggregatedDataUpdateJob {
+                                    upper_ids,
+                                    update: diff,
+                                }
+                                .into(),
+                            )
+                        }
+                    }
+                    if !followers.is_empty() {
+                        self.push(AggregationUpdateJob::InnerOfUpperLostFollowers {
+                            upper_id,
+                            lost_follower_ids: followers,
+                            retry: 0,
+                        });
+                    }
+                } else {
+                    drop(follower);
                 }
-                Some(old - 1)
+                follower_in_upper
             });
-            if remove_upper {
-                let data = AggregatedDataUpdate::from_task(&mut follower).invert();
-                let followers = get_followers(&follower);
-                drop(follower);
+            if lost_follower_ids.is_empty() {
+                return ControlFlow::Break(());
+            }
+            swap_retain(&mut lost_follower_ids, |&mut lost_follower_id| {
+                let mut upper = ctx.task(
+                    upper_id,
+                    // For performance reasons this should stay `Meta` and not `All`
+                    TaskDataCategory::Meta,
+                );
+                let mut inner_in_upper = false;
+                let mut removed_follower = false;
+                update!(
+                    upper,
+                    Follower {
+                        task: lost_follower_id
+                    },
+                    |old| {
+                        let Some(old) = old else {
+                            inner_in_upper = true;
+                            return None;
+                        };
+                        if old < 0 {
+                            inner_in_upper = true;
+                            return Some(old);
+                        }
+                        if old == 1 {
+                            removed_follower = true;
+                            return None;
+                        }
+                        Some(old - 1)
+                    }
+                );
+                if removed_follower {
+                    // May optimize the task
+                    if count!(upper, Follower).is_power_of_two() {
+                        self.push_optimize_task(upper_id);
+                    }
 
-                if !data.is_empty() {
-                    // remove data from upper
-                    let mut upper = ctx.task(
-                        upper_id,
-                        // For performance reasons this should stay `Meta` and not `All`
-                        TaskDataCategory::Meta,
-                    );
-                    let diff = data.apply(
-                        &mut upper,
-                        ctx.session_id(),
-                        ctx.should_track_activeness(),
-                        self,
-                    );
-                    if !diff.is_empty() {
-                        let upper_ids = get_uppers(&upper);
-                        self.push(
-                            AggregatedDataUpdateJob {
-                                upper_ids,
-                                update: diff,
-                            }
-                            .into(),
-                        )
+                    let upper_ids = get_uppers(&upper);
+                    let has_active_count =
+                        get!(upper, Activeness).is_some_and(|a| a.active_counter > 0);
+                    drop(upper);
+                    // update active count
+                    if has_active_count {
+                        self.push(AggregationUpdateJob::DecreaseActiveCount {
+                            task: lost_follower_id,
+                        });
+                    }
+                    // notify uppers about new follower
+                    if !upper_ids.is_empty() {
+                        self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
+                            upper_ids,
+                            lost_follower_id,
+                            retry: 0,
+                        });
                     }
                 }
-                if !followers.is_empty() {
-                    self.push(AggregationUpdateJob::InnerOfUpperLostFollowers {
-                        upper_id,
-                        lost_follower_ids: followers,
-                    });
-                }
-            } else {
-                drop(follower);
+                inner_in_upper
+            });
+            if lost_follower_ids.is_empty() {
+                return ControlFlow::Break(());
             }
-            follower_in_upper
+            ControlFlow::Continue(())
         });
-        for lost_follower_id in lost_follower_ids {
-            let mut upper = ctx.task(
-                upper_id,
-                // For performance reasons this should stay `Meta` and not `All`
-                TaskDataCategory::Meta,
-            );
-            if update_count!(
-                upper,
-                Follower {
-                    task: lost_follower_id
-                },
-                -1
-            ) {
-                // May optimize the task
-                if count!(upper, Follower).is_power_of_two() {
-                    self.push_optimize_task(upper_id);
-                }
-
-                let upper_ids = get_uppers(&upper);
-                let has_active_count =
-                    get!(upper, Activeness).is_some_and(|a| a.active_counter > 0);
-                drop(upper);
-                // update active count
-                if has_active_count {
-                    self.push(AggregationUpdateJob::DecreaseActiveCount {
-                        task: lost_follower_id,
-                    });
-                }
-                // notify uppers about new follower
-                if !upper_ids.is_empty() {
-                    self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
-                        upper_ids,
-                        lost_follower_id,
-                    });
-                }
+        if result.is_err() {
+            retry += 1;
+            if retry > MAX_RETRIES {
+                panic!(
+                    "inner_of_upper_lost_followers is not able to remove followers \
+                     {lost_follower_ids:?} from {upper_id:?} as they don't exist as upper or \
+                     follower edges"
+                )
             }
+            self.push(AggregationUpdateJob::InnerOfUpperLostFollowers {
+                upper_id,
+                lost_follower_ids,
+                retry,
+            });
         }
     }
 
@@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
+        if let Some(t) = time {
+            if t.elapsed() > MAX_YIELD_DURATION {
+                return Err(RetryTimeout);
+            }
+        } else {
+            time = Some(Instant::now());
+        }
+    }
+}
diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs
index 0d6092884581..634226d61c4e 100644
--- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs
+++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs
@@ -98,6 +98,7 @@ impl Operation for CleanupOldEdgesOperation {
                                     queue.push(AggregationUpdateJob::InnerOfUpperLostFollowers {
                                         upper_id: task_id,
                                         lost_follower_ids: children,
+                                        retry: 0,
                                     });
                                 } else {
                                     let upper_ids = get_uppers(&task);


Reviews:
user: login: bgw
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T21:09:54Z
user: login: bgw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T21:42:53Z
user: login: sokra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T22:36:06Z
user: login: lukesandberg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T23:46:04Z
user: login: bgw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T00:30:08Z
user: login: sokra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T08:10:40Z
user: login: sokra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T08:11:53Z

Review comments:
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: bgw
body: ```suggestion
/// Returns an error if the function was not able to complete and the timeout was reached.
```
created_at: 2025-05-21T20:44:36Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: bgw
body: ```suggestion
/// update might even be in the current thread and in the same queue. If that's the case yielding
```
created_at: 2025-05-21T20:44:58Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
+        if let Some(t) = time {
+            if t.elapsed() > MAX_YIELD_DURATION {
+                return Err(RetryTimeout);
+            }
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: bgw
body: Instead of a yield loop, would it be reasonable instead of have a refcount-style atomic u64 of currently executing modifications, and then you just use `EventListener` to wake up tasks when that count becomes zero?
created_at: 2025-05-21T20:49:33Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: bgw
body: This `thread::yield_now` and not `tokio::task::yield_now`. Is it possible that we're blocked on another task in the same thread? I know that task-stealing would cause that to *eventually* get executed by another thread, but that might be long after `MAX_YIELD_DURATION`.

If we can't turn this into an async function, wrapping this loop in `block_in_place` is hacky, but might at least be better: https://docs.rs/tokio/latest/tokio/task/fn.block_in_place.html
created_at: 2025-05-21T20:52:51Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
+        if let Some(t) = time {
+            if t.elapsed() > MAX_YIELD_DURATION {
+                return Err(RetryTimeout);
+            }
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: bgw
body: Upon further thought, what I've described here might actually be better represented using a global `RwLock<()>` where we normally/optimistically take a read lock for the modification, and fall back to taking a write lock when we need to guarantee exclusivity.
created_at: 2025-05-21T21:42:38Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: sokra
body: Thread yield is correct here. All aggregation update operations are sync and don't use async.
created_at: 2025-05-21T22:36:06Z
author_association: MEMBER
diff_hunk: @@ -1273,119 +1282,163 @@ impl AggregationUpdateQueue {
         ctx: &mut impl ExecuteContext,
         lost_follower_id: TaskId,
         mut upper_ids: TaskIdVec,
+        mut retry: u16,
     ) {
         #[cfg(feature = "trace_aggregation_update")]
         let _span = trace_span!("lost follower (n uppers)", uppers = upper_ids.len()).entered();
 
-        let mut follower = ctx.task(
-            lost_follower_id,
-            // For performance reasons this should stay `Meta` and not `All`
-            TaskDataCategory::Meta,
-        );
-        let mut follower_in_upper_ids = Vec::new();
-        let mut persistent_uppers = 0;
-        upper_ids.retain(|&mut upper_id| {
-            let mut keep_upper = false;
-            update!(follower, Upper { task: upper_id }, |old| {
-                let Some(old) = old else {
-                    follower_in_upper_ids.push(upper_id);
-                    return None;
-                };
-                if old < 0 {
-                    follower_in_upper_ids.push(upper_id);
-                    return Some(old);
-                }
-                if old == 1 {
-                    keep_upper = true;
-                    if !upper_id.is_transient() {
-                        persistent_uppers += 1;
+        // see documentation of `retry_loop` for more information why this is needed
+        let result = retry_loop(|| {
+            let mut follower = ctx.task(
+                lost_follower_id,
+                // For performance reasons this should stay `Meta` and not `All`
+                TaskDataCategory::Meta,
+            );
+            let mut removed_uppers = SmallVec::new();
+            swap_retain(&mut upper_ids, |&mut upper_id| {
+                let mut keep_upper = false;
+                let mut follower_in_upper = false;
+
+                update!(follower, Upper { task: upper_id }, |old| {
+                    let Some(old) = old else {
+                        follower_in_upper = true;
+                        return None;
+                    };
+                    if old < 0 {
+                        follower_in_upper = true;
+                        return Some(old);
                     }
-                    return None;
-                }
-                Some(old - 1)
+                    if old == 1 {
+                        keep_upper = true;
+                        removed_uppers.push(upper_id);
+                        return None;
+                    }
+                    Some(old - 1)
+                });
+                follower_in_upper
             });
-            keep_upper
-        });
-        if !upper_ids.is_empty() {
-            let data = AggregatedDataUpdate::from_task(&mut follower).invert();
-            let followers = get_followers(&follower);
-            drop(follower);
+            if !removed_uppers.is_empty() {
+                let data = AggregatedDataUpdate::from_task(&mut follower).invert();
+                let followers = get_followers(&follower);
+                drop(follower);
 
-            if !data.is_empty() {
-                for upper_id in upper_ids.iter() {
-                    // remove data from upper
-                    let mut upper = ctx.task(
-                        *upper_id,
-                        // For performance reasons this should stay `Meta` and not `All`
-                        TaskDataCategory::Meta,
-                    );
-                    let diff = data.apply(
-                        &mut upper,
-                        ctx.session_id(),
-                        ctx.should_track_activeness(),
-                        self,
-                    );
-                    if !diff.is_empty() {
-                        let upper_ids = get_uppers(&upper);
-                        self.push(
-                            AggregatedDataUpdateJob {
-                                upper_ids,
-                                update: diff,
-                            }
-                            .into(),
-                        )
+                if !data.is_empty() {
+                    for upper_id in removed_uppers.iter() {
+                        // remove data from upper
+                        let mut upper = ctx.task(
+                            *upper_id,
+                            // For performance reasons this should stay `Meta` and not `All`
+                            TaskDataCategory::Meta,
+                        );
+                        let diff = data.apply(
+                            &mut upper,
+                            ctx.session_id(),
+                            ctx.should_track_activeness(),
+                            self,
+                        );
+                        if !diff.is_empty() {
+                            let upper_ids = get_uppers(&upper);
+                            self.push(
+                                AggregatedDataUpdateJob {
+                                    upper_ids,
+                                    update: diff,
+                                }
+                                .into(),
+                            )
+                        }
                     }
                 }
+                if !followers.is_empty() {
+                    self.push(
+                        InnerOfUppersLostFollowersJob {
+                            upper_ids: removed_uppers.clone(),
+                            lost_follower_ids: followers,
+                        }
+                        .into(),
+                    );
+                }
+            } else {
+                drop(follower);
             }
-            if !followers.is_empty() {
-                self.push(
-                    InnerOfUppersLostFollowersJob {
-                        upper_ids: upper_ids.clone(),
-                        lost_follower_ids: followers,
+
+            if upper_ids.is_empty() {
+                return ControlFlow::Break(());
+            }
+            swap_retain(&mut upper_ids, |&mut upper_id| {
+                let mut upper = ctx.task(
+                    upper_id,
+                    // For performance reasons this should stay `Meta` and not `All`
+                    TaskDataCategory::Meta,
+                );
+                let mut inner_in_upper = false;
+                let mut removed_follower = false;
+                update!(
+                    upper,
+                    Follower {
+                        task: lost_follower_id
+                    },
+                    |old| {
+                        let Some(old) = old else {
+                            inner_in_upper = true;
+                            return None;
+                        };
+                        if old < 0 {
+                            inner_in_upper = true;
+                            return Some(old);
+                        }
+                        if old == 1 {
+                            removed_follower = true;
+                            return None;
+                        }
+                        Some(old - 1)
                     }
-                    .into(),
                 );
-            }
-        } else {
-            drop(follower);
-        }
-
-        for upper_id in follower_in_upper_ids {
-            let mut upper = ctx.task(
-                upper_id,
-                // For performance reasons this should stay `Meta` and not `All`
-                TaskDataCategory::Meta,
-            );
-            if update_count!(
-                upper,
-                Follower {
-                    task: lost_follower_id
-                },
-                -1
-            ) {
-                // May optimize the task
-                if count!(upper, Follower).is_power_of_two() {
-                    self.push_optimize_task(upper_id);
-                }
+                if removed_follower {
+                    // May optimize the task
+                    if count!(upper, Follower).is_power_of_two() {
+                        self.push_optimize_task(upper_id);
+                    }
 
-                let has_active_count = ctx.should_track_activeness()
-                    && get!(upper, Activeness).is_some_and(|a| a.active_counter > 0);
-                let upper_ids = get_uppers(&upper);
-                drop(upper);
-                // update active count
-                if has_active_count {
-                    self.push(AggregationUpdateJob::DecreaseActiveCount {
-                        task: lost_follower_id,
-                    });
-                }
-                // notify uppers about new follower
-                if !upper_ids.is_empty() {
-                    self.push(AggregationUpdateJob::InnerOfUppersLostFollower {
-                        upper_ids,
-                        lost_follower_id,
-                    });
+                    let has_active_count = ctx.should_track_activeness()
+                        && get!(upper, Activeness).is_some_and(|a| a.active_counter > 0);
+                    let upper_ids = get_uppers(&upper);
+                    drop(upper);
+                    // update active count
+                    if has_active_count {
+                        self.push(AggregationUpdateJob::DecreaseActiveCount {
+                            task: lost_follower_id,
+                        });
+                    }
+                    // notify uppers about new follower
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: lukesandberg
body: ```suggestion
                    // notify uppers about lost follower
```
created_at: 2025-05-21T23:46:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: bgw
body: Ah, I guess even though this would be called from an async task, no update that we might be waiting on would span across an await point.
created_at: 2025-05-22T00:30:08Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
+        if let Some(t) = time {
+            if t.elapsed() > MAX_YIELD_DURATION {
+                return Err(RetryTimeout);
+            }
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: sokra
body: That might deadlock if two updates wait for each other
created_at: 2025-05-22T08:10:40Z
author_association: MEMBER
diff_hunk: @@ -2355,3 +2454,40 @@ impl Operation for AggregationUpdateQueue {
         }
     }
 }
+
+struct RetryTimeout;
+
+const MAX_YIELD_DURATION: Duration = Duration::from_millis(1);
+const MAX_RETRIES: u16 = 10000;
+
+/// Retry the passed function for a few milliseconds, while yielding to other threads.
+/// Returns an error if the function was not ablue to complete and the timeout was reached.
+///
+/// Each graph modification will only lock one or two tasks at a time, but updates usually also
+/// require follow-up updates to connected tasks. So an update will "slowly" propagate through the
+/// graph. This can lead to the race condition when one update adds something and another update
+/// removes the same thing. The "add" update might not be fully propagated through the graph yet and
+/// the "remove" update can overtake the "add" update. When this happens the "remove" update is
+/// unable to remove the things it wants to remove (because they have not been added by the "add"
+/// update yet). So we will retry (with this method) removals until the thing is there. So this is
+/// basically a busy loop that waits for the "add" update to complete. If the busy loop is not
+/// sucessful, the update is added to the end of the queue again. This is important as the "add"
+/// update might even be in the curreent thread and in the same queue. If that's the case yielding
+/// won't help and the update need to be requeued.
+fn retry_loop(mut f: impl FnMut() -> ControlFlow<()>) -> Result<(), RetryTimeout> {
+    let mut time: Option<Instant> = None;
+    loop {
+        match f() {
+            ControlFlow::Continue(()) => {}
+            ControlFlow::Break(()) => return Ok(()),
+        }
+        yield_now();
path: turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs
user: login: sokra
body: thread yield is correct here, since the whole updating logic is sync and doesn't contain any await.
created_at: 2025-05-22T08:11:53Z
author_association: MEMBER

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-21T12:50:56Z
body: ###  ⚠️  No Changeset found

Latest commit: 93a75e846158859cba6baf5b76f7740df6b88666

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/vercel/next.js/new/sokra/avoid-negative-counts?filename=.changeset/sour-worms-guess.md&value=---%0A%0A---%0A%0ATurbopack%3A%20avoid%20negative%20uppers%20and%20follower%20and%20use%20retry%20loop%20instead%0A)


author_association: NONE
user: login: sokra
created_at: 2025-05-21T12:51:09Z
body: * **#79451** <a href="https://app.graphite.dev/github/pr/vercel/next.js/79451?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/vercel/next.js/79451?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `canary`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: ijjk
created_at: 2025-05-21T13:03:04Z
body: # Stats from current PR

<details open>
<summary><strong>Default Build</strong> (Increase detected ⚠️)</summary>

<br/>

<details>
<summary><strong>General</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| buildDuration | 17.9s | 15.5s | N/A |
| buildDurationCached | 14.7s | 12.5s | N/A |
| nodeModulesSize | 427 MB | 427 MB | ✓ |
| nextStartRea..uration (ms) | 413ms | 414ms | N/A |

</details>

<details>
<summary><strong>Client Bundles (main, webpack)</strong> Overall increase ⚠️</summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| 1733-HASH.js gzip | 44.5 kB | 45 kB | ⚠️ +470 B |
| 194b18f3-HASH.js gzip | 53.8 kB | 53.8 kB | N/A |
| 2192.HASH.js gzip | 169 B | 169 B | ✓ |
| 4719-HASH.js gzip | 5.47 kB | 5.44 kB | N/A |
| framework-HASH.js gzip | 57.4 kB | 57.4 kB | ✓ |
| main-app-HASH.js gzip | 254 B | 255 B | N/A |
| main-HASH.js gzip | 33.5 kB | 33.6 kB | N/A |
| webpack-HASH.js gzip | 1.71 kB | 1.71 kB | N/A |
| Overall change | 102 kB | 103 kB | ⚠️ +470 B |

</details>

<details>
<summary><strong>Legacy Client Bundles (polyfills)</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| polyfills-HASH.js gzip | 39.4 kB | 39.4 kB | ✓ |
| Overall change | 39.4 kB | 39.4 kB | ✓ |

</details>

<details>
<summary><strong>Client Pages</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| _app-HASH.js gzip | 193 B | 193 B | ✓ |
| _error-HASH.js gzip | 192 B | 192 B | ✓ |
| amp-HASH.js gzip | 510 B | 510 B | ✓ |
| css-HASH.js gzip | 344 B | 342 B | N/A |
| dynamic-HASH.js gzip | 1.84 kB | 1.83 kB | N/A |
| edge-ssr-HASH.js gzip | 265 B | 265 B | ✓ |
| head-HASH.js gzip | 363 B | 364 B | N/A |
| hooks-HASH.js gzip | 392 B | 391 B | N/A |
| image-HASH.js gzip | 4.69 kB | 4.67 kB | N/A |
| index-HASH.js gzip | 267 B | 267 B | ✓ |
| link-HASH.js gzip | 2.53 kB | 2.53 kB | N/A |
| routerDirect..HASH.js gzip | 328 B | 325 B | N/A |
| script-HASH.js gzip | 396 B | 396 B | ✓ |
| withRouter-HASH.js gzip | 325 B | 325 B | ✓ |
| 1afbb74e6ecf..834.css gzip | 106 B | 106 B | ✓ |
| Overall change | 2.25 kB | 2.25 kB | ✓ |

</details>

<details>
<summary><strong>Client Build Manifests</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| _buildManifest.js gzip | 751 B | 750 B | N/A |
| Overall change | 0 B | 0 B | ✓ |

</details>

<details>
<summary><strong>Rendered Page Sizes</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| index.html gzip | 530 B | 531 B | N/A |
| link.html gzip | 540 B | 541 B | N/A |
| withRouter.html gzip | 525 B | 524 B | N/A |
| Overall change | 0 B | 0 B | ✓ |

</details>

<details>
<summary><strong>Edge SSR bundle Size</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| edge-ssr.js gzip | 141 kB | 141 kB | N/A |
| page.js gzip | 220 kB | 214 kB | N/A |
| Overall change | 0 B | 0 B | ✓ |

</details>

<details>
<summary><strong>Middleware size</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| middleware-b..fest.js gzip | 676 B | 674 B | N/A |
| middleware-r..fest.js gzip | 155 B | 157 B | N/A |
| middleware.js gzip | 32.5 kB | 32.5 kB | N/A |
| edge-runtime..pack.js gzip | 853 B | 853 B | ✓ |
| Overall change | 853 B | 853 B | ✓ |

</details>

<details>
<summary><strong>Next Runtimes</strong></summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| app-page-exp...dev.js gzip | 322 kB | 322 kB | ✓ |
| app-page-exp..prod.js gzip | 150 kB | 150 kB | ✓ |
| app-page-tur...dev.js gzip | 322 kB | 322 kB | N/A |
| app-page-tur..prod.js gzip | 150 kB | 150 kB | ✓ |
| app-page-tur...dev.js gzip | 313 kB | 313 kB | N/A |
| app-page-tur..prod.js gzip | 146 kB | 146 kB | ✓ |
| app-page.run...dev.js gzip | 313 kB | 313 kB | N/A |
| app-page.run..prod.js gzip | 146 kB | 146 kB | ✓ |
| app-route-ex...dev.js gzip | 60.5 kB | 60.5 kB | ✓ |
| app-route-ex..prod.js gzip | 39.4 kB | 39.4 kB | ✓ |
| app-route-tu...dev.js gzip | 60.5 kB | 60.5 kB | ✓ |
| app-route-tu..prod.js gzip | 39.4 kB | 39.4 kB | ✓ |
| app-route-tu...dev.js gzip | 59.9 kB | 59.9 kB | ✓ |
| app-route-tu..prod.js gzip | 39.1 kB | 39.1 kB | ✓ |
| app-route.ru...dev.js gzip | 59.9 kB | 59.9 kB | ✓ |
| app-route.ru..prod.js gzip | 39.1 kB | 39.1 kB | ✓ |
| dist_client_...dev.js gzip | 326 B | 326 B | ✓ |
| dist_client_...dev.js gzip | 328 B | 328 B | ✓ |
| dist_client_...dev.js gzip | 320 B | 320 B | ✓ |
| dist_client_...dev.js gzip | 318 B | 318 B | ✓ |
| pages-api-tu...dev.js gzip | 30 kB | 30 kB | ✓ |
| pages-api-tu..prod.js gzip | 21.4 kB | 21.4 kB | ✓ |
| pages-api.ru...dev.js gzip | 30 kB | 30 kB | ✓ |
| pages-api.ru..prod.js gzip | 21.4 kB | 21.4 kB | ✓ |
| pages-turbo....dev.js gzip | 43.7 kB | 43.7 kB | ✓ |
| pages-turbo...prod.js gzip | 32 kB | 32 kB | ✓ |
| pages.runtim...dev.js gzip | 43.9 kB | 43.9 kB | ✓ |
| pages.runtim..prod.js gzip | 32.1 kB | 32.1 kB | ✓ |
| server.runti..prod.js gzip | 67 kB | 67 kB | ✓ |
| Overall change | 1.63 MB | 1.63 MB | ✓ |

</details>

<details>
<summary><strong>build cache</strong> Overall increase ⚠️</summary>

|  | vercel/next.js canary  | vercel/next.js sokra/avoid-negative-counts | Change |
| - | - | - | - |
| 0.pack gzip | 2.48 MB | 2.49 MB | ⚠️ +2.69 kB |
| index.pack gzip | 78.2 kB | 78.7 kB | ⚠️ +465 B |
| Overall change | 2.56 MB | 2.56 MB | ⚠️ +3.15 kB |

</details>

<details>
<summary><strong>Diff details</strong></summary>

<details>
<summary>Diff for <strong>page.js</strong></summary>

Diff too large to display
</details>
<details>
<summary>Diff for <strong>middleware.js</strong></summary>

Diff too large to display
</details>
<details>
<summary>Diff for <strong>edge-ssr.js</strong></summary>

Diff too large to display
</details>
<details>
<summary>Diff for <strong>css-HASH.js</strong></summary>

```diff
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9813],
   {
-    /***/ 1586: /***/ (
+    /***/ 2628: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/css",
         function () {
-          return __webpack_require__(4362);
+          return __webpack_require__(8707);
         },
       ]);
       if (false) {
@@ -18,14 +18,7 @@
       /***/
     },
 
-    /***/ 4350: /***/ (module) => {
-      // extracted by mini-css-extract-plugin
-      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
-
-      /***/
-    },
-
-    /***/ 4362: /***/ (
+    /***/ 8707: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -39,7 +32,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4350);
+        __webpack_require__(9080);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           _css_module_css__WEBPACK_IMPORTED_MODULE_1__
@@ -58,13 +51,20 @@
 
       /***/
     },
+
+    /***/ 9080: /***/ (module) => {
+      // extracted by mini-css-extract-plugin
+      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1586)
+      __webpack_exec__(2628)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>dynamic-HASH.js</strong></summary>

```diff
@@ -1,17 +1,117 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [2291],
   {
-    /***/ 283: /***/ (
+    /***/ 2001: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
+      __webpack_require__
+    ) => {
+      "use strict";
+      __webpack_require__.r(__webpack_exports__);
+      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
+        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
+        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
+        /* harmony export */
+      });
+      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
+        __webpack_require__(5640);
+      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
+        __webpack_require__(9553);
+      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
+        /*#__PURE__*/ __webpack_require__.n(
+          next_dynamic__WEBPACK_IMPORTED_MODULE_1__
+        );
+
+      const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
+        () =>
+          __webpack_require__
+            .e(/* import() */ 8042)
+            .then(__webpack_require__.bind(__webpack_require__, 8042))
+            .then((mod) => mod.Hello),
+        {
+          loadableGenerated: {
+            webpack: () => [/*require.resolve*/ 8042],
+          },
+        }
+      );
+      const Page = () =>
+        /*#__PURE__*/ (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(
+          react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment,
+          {
+            children: [
+              /*#__PURE__*/ (0,
+              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
+                children: "testing next/dynamic size",
+              }),
+              /*#__PURE__*/ (0,
+              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
+                DynamicHello,
+                {}
+              ),
+            ],
+          }
+        );
+      var __N_SSP = true;
+      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = Page;
+
+      /***/
+    },
+
+    /***/ 2976: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/dynamic",
+        function () {
+          return __webpack_require__(2001);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 7807: /***/ (
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) => {
+      "use strict";
+      /* __next_internal_client_entry_do_not_use__  cjs */
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "LoadableContext", {
+        enumerable: true,
+        get: function () {
+          return LoadableContext;
+        },
+      });
+      const _interop_require_default = __webpack_require__(1532);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(148)
+      );
+      const LoadableContext = _react.default.createContext(null);
+      if (false) {
+      } //# sourceMappingURL=loadable-context.shared-runtime.js.map
+
+      /***/
+    },
+
+    /***/ 9553: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(6990);
+      module.exports = __webpack_require__(9986);
 
       /***/
     },
 
-    /***/ 505: /***/ (
+    /***/ 9829: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -53,7 +153,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(148)
       );
-      const _loadablecontextsharedruntime = __webpack_require__(6179);
+      const _loadablecontextsharedruntime = __webpack_require__(7807);
       function resolve(obj) {
         return obj && obj.default ? obj.default : obj;
       }
@@ -288,90 +388,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 5703: /***/ (
-      __unused_webpack_module,
-      __webpack_exports__,
-      __webpack_require__
-    ) => {
-      "use strict";
-      __webpack_require__.r(__webpack_exports__);
-      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
-        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
-        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
-        /* harmony export */
-      });
-      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(5640);
-      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(283);
-      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
-        /*#__PURE__*/ __webpack_require__.n(
-          next_dynamic__WEBPACK_IMPORTED_MODULE_1__
-        );
-
-      const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
-        () =>
-          __webpack_require__
-            .e(/* import() */ 2192)
-            .then(__webpack_require__.bind(__webpack_require__, 2192))
-            .then((mod) => mod.Hello),
-        {
-          loadableGenerated: {
-            webpack: () => [/*require.resolve*/ 2192],
-          },
-        }
-      );
-      const Page = () =>
-        /*#__PURE__*/ (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(
-          react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment,
-          {
-            children: [
-              /*#__PURE__*/ (0,
-              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
-                children: "testing next/dynamic size",
-              }),
-              /*#__PURE__*/ (0,
-              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
-                DynamicHello,
-                {}
-              ),
-            ],
-          }
-        );
-      var __N_SSP = true;
-      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = Page;
-
-      /***/
-    },
-
-    /***/ 6179: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      "use strict";
-      /* __next_internal_client_entry_do_not_use__  cjs */
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "LoadableContext", {
-        enumerable: true,
-        get: function () {
-          return LoadableContext;
-        },
-      });
-      const _interop_require_default = __webpack_require__(1532);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(148)
-      );
-      const LoadableContext = _react.default.createContext(null);
-      if (false) {
-      } //# sourceMappingURL=loadable-context.shared-runtime.js.map
-
-      /***/
-    },
-
-    /***/ 6990: /***/ (module, exports, __webpack_require__) => {
+    /***/ 9986: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -404,7 +421,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
         __webpack_require__(148)
       );
       const _loadablesharedruntime = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(505)
+        __webpack_require__(9829)
       );
       const isServerSide = "object" === "undefined";
       // Normalize loader to return the module as form { default: Component } for `React.lazy`.
@@ -504,30 +521,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
 
       /***/
     },
-
-    /***/ 9254: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/dynamic",
-        function () {
-          return __webpack_require__(5703);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9254)
+      __webpack_exec__(2976)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>hooks-HASH.js</strong></summary>

```diff
@@ -1,24 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9804],
   {
-    /***/ 1664: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/hooks",
-        function () {
-          return __webpack_require__(6130);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 6130: /***/ (
+    /***/ 4756: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -76,13 +59,30 @@
 
       /***/
     },
+
+    /***/ 5426: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/hooks",
+        function () {
+          return __webpack_require__(4756);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1664)
+      __webpack_exec__(5426)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>image-HASH.js</strong></summary>

```diff
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [2983],
   {
-    /***/ 2198: /***/ (
+    /***/ 264: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/image",
         function () {
-          return __webpack_require__(3444);
+          return __webpack_require__(3594);
         },
       ]);
       if (false) {
@@ -18,7 +18,190 @@
       /***/
     },
 
-    /***/ 2514: /***/ (
+    /***/ 1206: /***/ (__unused_webpack_module, exports) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "default", {
+        enumerable: true,
+        get: function () {
+          return _default;
+        },
+      });
+      const DEFAULT_Q = 75;
+      function defaultLoader(param) {
+        let { config, src, width, quality } = param;
+        var _config_qualities;
+        if (false) {
+        }
+        const q =
+          quality ||
+          ((_config_qualities = config.qualities) == null
+            ? void 0
+            : _config_qualities.reduce((prev, cur) =>
+                Math.abs(cur - DEFAULT_Q) < Math.abs(prev - DEFAULT_Q)
+                  ? cur
+                  : prev
+              )) ||
+          DEFAULT_Q;
+        return (
+          config.path +
+          "?url=" +
+          encodeURIComponent(src) +
+          "&w=" +
+          width +
+          "&q=" +
+          q +
+          (src.startsWith("/_next/static/media/") && false ? 0 : "")
+        );
+      }
+      // We use this to determine if the import is the default loader
+      // or a custom loader defined by the user in next.config.js
+      defaultLoader.__next_img_default = true;
+      const _default = defaultLoader; //# sourceMappingURL=image-loader.js.map
+
+      /***/
+    },
+
+    /***/ 1765: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "useMergedRef", {
+        enumerable: true,
+        get: function () {
+          return useMergedRef;
+        },
+      });
+      const _react = __webpack_require__(148);
+      function useMergedRef(refA, refB) {
+        const cleanupA = (0, _react.useRef)(null);
+        const cleanupB = (0, _react.useRef)(null);
+        // NOTE: In theory, we could skip the wrapping if only one of the refs is non-null.
+        // (this happens often if the user doesn't pass a ref to Link/Form/Image)
+        // But this can cause us to leak a cleanup-ref into user code (e.g. via `<Link legacyBehavior>`),
+        // and the user might pass that ref into ref-merging library that doesn't support cleanup refs
+        // (because it hasn't been updated for React 19)
+        // which can then cause things to blow up, because a cleanup-returning ref gets called with `null`.
+        // So in practice, it's safer to be defensive and always wrap the ref, even on React 19.
+        return (0, _react.useCallback)(
+          (current) => {
+            if (current === null) {
+              const cleanupFnA = cleanupA.current;
+              if (cleanupFnA) {
+                cleanupA.current = null;
+                cleanupFnA();
+              }
+              const cleanupFnB = cleanupB.current;
+              if (cleanupFnB) {
+                cleanupB.current = null;
+                cleanupFnB();
+              }
+            } else {
+              if (refA) {
+                cleanupA.current = applyRef(refA, current);
+              }
+              if (refB) {
+                cleanupB.current = applyRef(refB, current);
+              }
+            }
+          },
+          [refA, refB]
+        );
+      }
+      function applyRef(refA, current) {
+        if (typeof refA === "function") {
+          const cleanup = refA(current);
+          if (typeof cleanup === "function") {
+            return cleanup;
+          } else {
+            return () => refA(null);
+          }
+        } else {
+          refA.current = current;
+          return () => {
+            refA.current = null;
+          };
+        }
+      }
+      if (
+        (typeof exports.default === "function" ||
+          (typeof exports.default === "object" && exports.default !== null)) &&
+        typeof exports.default.__esModule === "undefined"
+      ) {
+        Object.defineProperty(exports.default, "__esModule", {
+          value: true,
+        });
+        Object.assign(exports.default, exports);
+        module.exports = exports.default;
+      } //# sourceMappingURL=use-merged-ref.js.map
+
+      /***/
+    },
+
+    /***/ 3353: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(5526);
+
+      /***/
+    },
+
+    /***/ 3594: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
+      __webpack_require__
+    ) => {
+      "use strict";
+      // ESM COMPAT FLAG
+      __webpack_require__.r(__webpack_exports__);
+
+      // EXPORTS
+      __webpack_require__.d(__webpack_exports__, {
+        __N_SSP: () => /* binding */ __N_SSP,
+        default: () => /* binding */ pages_image,
+      });
+
+      // EXTERNAL MODULE: ./node_modules/.pnpm/react@19.1.0/node_modules/react/jsx-runtime.js
+      var jsx_runtime = __webpack_require__(5640);
+      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+diff-repo+packages+next+next-packed.tgz_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/image.js
+      var next_image = __webpack_require__(3353);
+      var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // ./pages/nextjs.png
+      /* harmony default export */ const nextjs = {
+        src: "/_next/static/media/nextjs.cae0b805.png",
+        height: 1347,
+        width: 1626,
+        blurDataURL:
+          "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAMAAAACh/xsAAAAD1BMVEX////x8fH6+vrb29vo6Oh8o70bAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAH0lEQVR4nGNgwARMjIyMjCAGCzMzMwsTRISJCcRABwAEcAAkLCQfgAAAAABJRU5ErkJggg==",
+        blurWidth: 8,
+        blurHeight: 7,
+      }; // ./pages/image.js
+      function ImagePage(props) {
+        return /*#__PURE__*/ (0, jsx_runtime.jsxs)(jsx_runtime.Fragment, {
+          children: [
+            /*#__PURE__*/ (0, jsx_runtime.jsx)("h1", {
+              children: "next/image example",
+            }),
+            /*#__PURE__*/ (0, jsx_runtime.jsx)(image_default(), {
+              src: nextjs,
+              placeholder: "blur",
+            }),
+          ],
+        });
+      }
+      var __N_SSP = true;
+      /* harmony default export */ const pages_image = ImagePage;
+
+      /***/
+    },
+
+    /***/ 3854: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -34,9 +217,9 @@
           return getImgProps;
         },
       });
-      const _warnonce = __webpack_require__(5127);
-      const _imageblursvg = __webpack_require__(4287);
-      const _imageconfig = __webpack_require__(2795);
+      const _warnonce = __webpack_require__(3603);
+      const _imageblursvg = __webpack_require__(7835);
+      const _imageconfig = __webpack_require__(6799);
       const VALID_LOADING_VALUES =
         /* unused pure expression or super */ null && [
           "lazy",
@@ -463,55 +646,69 @@
       /***/
     },
 
-    /***/ 3444: /***/ (
+    /***/ 5526: /***/ (
       __unused_webpack_module,
-      __webpack_exports__,
+      exports,
       __webpack_require__
     ) => {
       "use strict";
-      // ESM COMPAT FLAG
-      __webpack_require__.r(__webpack_exports__);
 
-      // EXPORTS
-      __webpack_require__.d(__webpack_exports__, {
-        __N_SSP: () => /* binding */ __N_SSP,
-        default: () => /* binding */ pages_image,
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
       });
-
-      // EXTERNAL MODULE: ./node_modules/.pnpm/react@19.1.0/node_modules/react/jsx-runtime.js
-      var jsx_runtime = __webpack_require__(5640);
-      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+main-repo+packages+next+next-packed.tgz_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/image.js
-      var next_image = __webpack_require__(6359);
-      var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // ./pages/nextjs.png
-      /* harmony default export */ const nextjs = {
-        src: "/_next/static/media/nextjs.cae0b805.png",
-        height: 1347,
-        width: 1626,
-        blurDataURL:
-          "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAMAAAACh/xsAAAAD1BMVEX////x8fH6+vrb29vo6Oh8o70bAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAH0lEQVR4nGNgwARMjIyMjCAGCzMzMwsTRISJCcRABwAEcAAkLCQfgAAAAABJRU5ErkJggg==",
-        blurWidth: 8,
-        blurHeight: 7,
-      }; // ./pages/image.js
-      function ImagePage(props) {
-        return /*#__PURE__*/ (0, jsx_runtime.jsxs)(jsx_runtime.Fragment, {
-          children: [
-            /*#__PURE__*/ (0, jsx_runtime.jsx)("h1", {
-              children: "next/image example",
-            }),
-            /*#__PURE__*/ (0, jsx_runtime.jsx)(image_default(), {
-              src: nextjs,
-              placeholder: "blur",
-            }),
-          ],
+      0 && 0;
+      function _export(target, all) {
+        for (var name in all)
+          Object.defineProperty(target, name, {
+            enumerable: true,
+            get: all[name],
+          });
+      }
+      _export(exports, {
+        default: function () {
+          return _default;
+        },
+        getImageProps: function () {
+          return getImageProps;
+        },
+      });
+      const _interop_require_default = __webpack_require__(1532);
+      const _getimgprops = __webpack_require__(3854);
+      const _imagecomponent = __webpack_require__(8350);
+      const _imageloader = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(1206)
+      );
+      function getImageProps(imgProps) {
+        const { props } = (0, _getimgprops.getImgProps)(imgProps, {
+          defaultLoader: _imageloader.default,
+          // This is replaced by webpack define plugin
+          imgConf: {
+            deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
+            imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
+            path: "/_next/image",
+            loader: "default",
+            dangerouslyAllowSVG: false,
+            unoptimized: false,
+          },
         });
+        // Normally we don't care about undefined props because we pass to JSX,
+        // but this exported function could be used by the end user for anything
+        // so we delete undefined props to clean it up a little.
+        for (const [key, value] of Object.entries(props)) {
+          if (value === undefined) {
+            delete props[key];
+          }
+        }
+        return {
+          props,
+        };
       }
-      var __N_SSP = true;
-      /* harmony default export */ const pages_image = ImagePage;
+      const _default = _imagecomponent.Image; //# sourceMappingURL=image-external.js.map
 
       /***/
     },
 
-    /***/ 4287: /***/ (__unused_webpack_module, exports) => {
+    /***/ 7835: /***/ (__unused_webpack_module, exports) => {
       "use strict";
       /**
        * A shared function, used on both client and server, to generate a SVG blur placeholder.
@@ -566,85 +763,7 @@
       /***/
     },
 
-    /***/ 4985: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "useMergedRef", {
-        enumerable: true,
-        get: function () {
-          return useMergedRef;
-        },
-      });
-      const _react = __webpack_require__(148);
-      function useMergedRef(refA, refB) {
-        const cleanupA = (0, _react.useRef)(null);
-        const cleanupB = (0, _react.useRef)(null);
-        // NOTE: In theory, we could skip the wrapping if only one of the refs is non-null.
-        // (this happens often if the user doesn't pass a ref to Link/Form/Image)
-        // But this can cause us to leak a cleanup-ref into user code (e.g. via `<Link legacyBehavior>`),
-        // and the user might pass that ref into ref-merging library that doesn't support cleanup refs
-        // (because it hasn't been updated for React 19)
-        // which can then cause things to blow up, because a cleanup-returning ref gets called with `null`.
-        // So in practice, it's safer to be defensive and always wrap the ref, even on React 19.
-        return (0, _react.useCallback)(
-          (current) => {
-            if (current === null) {
-              const cleanupFnA = cleanupA.current;
-              if (cleanupFnA) {
-                cleanupA.current = null;
-                cleanupFnA();
-              }
-              const cleanupFnB = cleanupB.current;
-              if (cleanupFnB) {
-                cleanupB.current = null;
-                cleanupFnB();
-              }
-            } else {
-              if (refA) {
-                cleanupA.current = applyRef(refA, current);
-              }
-              if (refB) {
-                cleanupB.current = applyRef(refB, current);
-              }
-            }
-          },
-          [refA, refB]
-        );
-      }
-      function applyRef(refA, current) {
-        if (typeof refA === "function") {
-          const cleanup = refA(current);
-          if (typeof cleanup === "function") {
-            return cleanup;
-          } else {
-            return () => refA(null);
-          }
-        } else {
-          refA.current = current;
-          return () => {
-            refA.current = null;
-          };
-        }
-      }
-      if (
-        (typeof exports.default === "function" ||
-          (typeof exports.default === "object" && exports.default !== null)) &&
-        typeof exports.default.__esModule === "undefined"
-      ) {
-        Object.defineProperty(exports.default, "__esModule", {
-          value: true,
-        });
-        Object.assign(exports.default, exports);
-        module.exports = exports.default;
-      } //# sourceMappingURL=use-merged-ref.js.map
-
-      /***/
-    },
-
-    /***/ 5898: /***/ (module, exports, __webpack_require__) => {
+    /***/ 8350: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -666,17 +785,17 @@
         __webpack_require__(7897)
       );
       const _head = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(5207)
+        __webpack_require__(8843)
       );
-      const _getimgprops = __webpack_require__(2514);
-      const _imageconfig = __webpack_require__(2795);
-      const _imageconfigcontextsharedruntime = __webpack_require__(2349);
-      const _warnonce = __webpack_require__(5127);
-      const _routercontextsharedruntime = __webpack_require__(3556);
+      const _getimgprops = __webpack_require__(3854);
+      const _imageconfig = __webpack_require__(6799);
+      const _imageconfigcontextsharedruntime = __webpack_require__(3905);
+      const _warnonce = __webpack_require__(3603);
+      const _routercontextsharedruntime = __webpack_require__(6712);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(5970)
+        __webpack_require__(1206)
       );
-      const _usemergedref = __webpack_require__(4985);
+      const _usemergedref = __webpack_require__(1765);
       // This is replaced by webpack define plugin
       const configEnv = {
         deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
@@ -1002,132 +1121,13 @@
 
       /***/
     },
-
-    /***/ 5970: /***/ (__unused_webpack_module, exports) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "default", {
-        enumerable: true,
-        get: function () {
-          return _default;
-        },
-      });
-      const DEFAULT_Q = 75;
-      function defaultLoader(param) {
-        let { config, src, width, quality } = param;
-        var _config_qualities;
-        if (false) {
-        }
-        const q =
-          quality ||
-          ((_config_qualities = config.qualities) == null
-            ? void 0
-            : _config_qualities.reduce((prev, cur) =>
-                Math.abs(cur - DEFAULT_Q) < Math.abs(prev - DEFAULT_Q)
-                  ? cur
-                  : prev
-              )) ||
-          DEFAULT_Q;
-        return (
-          config.path +
-          "?url=" +
-          encodeURIComponent(src) +
-          "&w=" +
-          width +
-          "&q=" +
-          q +
-          (src.startsWith("/_next/static/media/") && false ? 0 : "")
-        );
-      }
-      // We use this to determine if the import is the default loader
-      // or a custom loader defined by the user in next.config.js
-      defaultLoader.__next_img_default = true;
-      const _default = defaultLoader; //# sourceMappingURL=image-loader.js.map
-
-      /***/
-    },
-
-    /***/ 6359: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(8986);
-
-      /***/
-    },
-
-    /***/ 8986: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      0 && 0;
-      function _export(target, all) {
-        for (var name in all)
-          Object.defineProperty(target, name, {
-            enumerable: true,
-            get: all[name],
-          });
-      }
-      _export(exports, {
-        default: function () {
-          return _default;
-        },
-        getImageProps: function () {
-          return getImageProps;
-        },
-      });
-      const _interop_require_default = __webpack_require__(1532);
-      const _getimgprops = __webpack_require__(2514);
-      const _imagecomponent = __webpack_require__(5898);
-      const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(5970)
-      );
-      function getImageProps(imgProps) {
-        const { props } = (0, _getimgprops.getImgProps)(imgProps, {
-          defaultLoader: _imageloader.default,
-          // This is replaced by webpack define plugin
-          imgConf: {
-            deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
-            imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
-            path: "/_next/image",
-            loader: "default",
-            dangerouslyAllowSVG: false,
-            unoptimized: false,
-          },
-        });
-        // Normally we don't care about undefined props because we pass to JSX,
-        // but this exported function could be used by the end user for anything
-        // so we delete undefined props to clean it up a little.
-        for (const [key, value] of Object.entries(props)) {
-          if (value === undefined) {
-            delete props[key];
-          }
-        }
-        return {
-          props,
-        };
-      }
-      const _default = _imagecomponent.Image; //# sourceMappingURL=image-external.js.map
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(2198)
+      __webpack_exec__(264)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>index-HASH.js</strong></summary>

```diff
@@ -1,7 +1,24 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [3332],
   {
-    /***/ 9418: /***/ (
+    /***/ 8230: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/",
+        function () {
+          return __webpack_require__(8696);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 8696: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -19,30 +36,13 @@
 
       /***/
     },
-
-    /***/ 9532: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/",
-        function () {
-          return __webpack_require__(9418);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9532)
+      __webpack_exec__(8230)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>link-HASH.js</strong></summary>

```diff
@@ -1,125 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [4672],
   {
-    /***/ 1854: /***/ (
-      __unused_webpack_module,
-      __webpack_exports__,
-      __webpack_require__
-    ) => {
-      "use strict";
-      __webpack_require__.r(__webpack_exports__);
-      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
-        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
-        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
-        /* harmony export */
-      });
-      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(5640);
-      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(8770);
-      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
-        /*#__PURE__*/ __webpack_require__.n(
-          next_link__WEBPACK_IMPORTED_MODULE_1__
-        );
-
-      function aLink(props) {
-        return /*#__PURE__*/ (0,
-        react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
-          children: [
-            /*#__PURE__*/ (0,
-            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h3", {
-              children: "A Link page!",
-            }),
-            /*#__PURE__*/ (0,
-            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
-              next_link__WEBPACK_IMPORTED_MODULE_1___default(),
-              {
-                href: "/",
-                children: "Go to /",
-              }
-            ),
-          ],
-        });
-      }
-      var __N_SSP = true;
-      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = aLink;
-
-      /***/
-    },
-
-    /***/ 3199: /***/ (__unused_webpack_module, exports) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "errorOnce", {
-        enumerable: true,
-        get: function () {
-          return errorOnce;
-        },
-      });
-      let errorOnce = (_) => {};
-      if (false) {
-      } //# sourceMappingURL=error-once.js.map
-
-      /***/
-    },
-
-    /***/ 3568: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/link",
-        function () {
-          return __webpack_require__(1854);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 3857: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "getDomainLocale", {
-        enumerable: true,
-        get: function () {
-          return getDomainLocale;
-        },
-      });
-      const _normalizetrailingslash = __webpack_require__(4869);
-      const basePath =
-        /* unused pure expression or super */ null && (false || "");
-      function getDomainLocale(path, locale, locales, domainLocales) {
-        if (false) {
-        } else {
-          return false;
-        }
-      }
-      if (
-        (typeof exports.default === "function" ||
-          (typeof exports.default === "object" && exports.default !== null)) &&
-        typeof exports.default.__esModule === "undefined"
-      ) {
-        Object.defineProperty(exports.default, "__esModule", {
-          value: true,
-        });
-        Object.assign(exports.default, exports);
-        module.exports = exports.default;
-      } //# sourceMappingURL=get-domain-locale.js.map
-
-      /***/
-    },
-
-    /***/ 3947: /***/ (module, exports, __webpack_require__) => {
+    /***/ 591: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -146,17 +28,17 @@
       const _react = /*#__PURE__*/ _interop_require_wildcard._(
         __webpack_require__(148)
       );
-      const _resolvehref = __webpack_require__(3161);
-      const _islocalurl = __webpack_require__(2309);
-      const _formaturl = __webpack_require__(3768);
-      const _utils = __webpack_require__(5554);
-      const _addlocale = __webpack_require__(7591);
-      const _routercontextsharedruntime = __webpack_require__(3556);
-      const _useintersection = __webpack_require__(5624);
-      const _getdomainlocale = __webpack_require__(3857);
-      const _addbasepath = __webpack_require__(4356);
-      const _usemergedref = __webpack_require__(4985);
-      const _erroronce = __webpack_require__(3199);
+      const _resolvehref = __webpack_require__(5837);
+      const _islocalurl = __webpack_require__(5953);
+      const _formaturl = __webpack_require__(6212);
+      const _utils = __webpack_require__(6950);
+      const _addlocale = __webpack_require__(6467);
+      const _routercontextsharedruntime = __webpack_require__(6712);
+      const _useintersection = __webpack_require__(9692);
+      const _getdomainlocale = __webpack_require__(6850);
+      const _addbasepath = __webpack_require__(4928);
+      const _usemergedref = __webpack_require__(1765);
+      const _erroronce = __webpack_require__(8659);
       const prefetched = new Set();
       function prefetch(router, href, as, options) {
         if (false) {
@@ -545,7 +427,17 @@
       /***/
     },
 
-    /***/ 4985: /***/ (module, exports, __webpack_require__) => {
+    /***/ 1148: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(591);
+
+      /***/
+    },
+
+    /***/ 1765: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -623,7 +515,125 @@
       /***/
     },
 
-    /***/ 5624: /***/ (module, exports, __webpack_require__) => {
+    /***/ 5436: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
+      __webpack_require__
+    ) => {
+      "use strict";
+      __webpack_require__.r(__webpack_exports__);
+      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
+        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
+        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
+        /* harmony export */
+      });
+      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
+        __webpack_require__(5640);
+      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
+        __webpack_require__(1148);
+      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
+        /*#__PURE__*/ __webpack_require__.n(
+          next_link__WEBPACK_IMPORTED_MODULE_1__
+        );
+
+      function aLink(props) {
+        return /*#__PURE__*/ (0,
+        react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
+          children: [
+            /*#__PURE__*/ (0,
+            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h3", {
+              children: "A Link page!",
+            }),
+            /*#__PURE__*/ (0,
+            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
+              next_link__WEBPACK_IMPORTED_MODULE_1___default(),
+              {
+                href: "/",
+                children: "Go to /",
+              }
+            ),
+          ],
+        });
+      }
+      var __N_SSP = true;
+      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = aLink;
+
+      /***/
+    },
+
+    /***/ 6850: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "getDomainLocale", {
+        enumerable: true,
+        get: function () {
+          return getDomainLocale;
+        },
+      });
+      const _normalizetrailingslash = __webpack_require__(6457);
+      const basePath =
+        /* unused pure expression or super */ null && (false || "");
+      function getDomainLocale(path, locale, locales, domainLocales) {
+        if (false) {
+        } else {
+          return false;
+        }
+      }
+      if (
+        (typeof exports.default === "function" ||
+          (typeof exports.default === "object" && exports.default !== null)) &&
+        typeof exports.default.__esModule === "undefined"
+      ) {
+        Object.defineProperty(exports.default, "__esModule", {
+          value: true,
+        });
+        Object.assign(exports.default, exports);
+        module.exports = exports.default;
+      } //# sourceMappingURL=get-domain-locale.js.map
+
+      /***/
+    },
+
+    /***/ 8659: /***/ (__unused_webpack_module, exports) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "errorOnce", {
+        enumerable: true,
+        get: function () {
+          return errorOnce;
+        },
+      });
+      let errorOnce = (_) => {};
+      if (false) {
+      } //# sourceMappingURL=error-once.js.map
+
+      /***/
+    },
+
+    /***/ 9666: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/link",
+        function () {
+          return __webpack_require__(5436);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 9692: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -636,7 +646,7 @@
         },
       });
       const _react = __webpack_require__(148);
-      const _requestidlecallback = __webpack_require__(3543);
+      const _requestidlecallback = __webpack_require__(315);
       const hasIntersectionObserver =
         typeof IntersectionObserver === "function";
       const observers = new Map();
@@ -748,23 +758,13 @@
 
       /***/
     },
-
-    /***/ 8770: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(3947);
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(3568)
+      __webpack_exec__(9666)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>routerDirect-HASH.js</strong></summary>

```diff
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [188],
   {
-    /***/ 3618: /***/ (
+    /***/ 76: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -16,7 +16,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4631);
+        __webpack_require__(9413);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_router__WEBPACK_IMPORTED_MODULE_1__
@@ -36,17 +36,7 @@
       /***/
     },
 
-    /***/ 4631: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(7086);
-
-      /***/
-    },
-
-    /***/ 7824: /***/ (
+    /***/ 1810: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -54,7 +44,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/routerDirect",
         function () {
-          return __webpack_require__(3618);
+          return __webpack_require__(76);
         },
       ]);
       if (false) {
@@ -62,13 +52,23 @@
 
       /***/
     },
+
+    /***/ 9413: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(5282);
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(7824)
+      __webpack_exec__(1810)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>script-HASH.js</strong></summary>

```diff
@@ -1,24 +1,17 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [1209],
   {
-    /***/ 1984: /***/ (
-      __unused_webpack_module,
+    /***/ 2227: /***/ (
+      module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/script",
-        function () {
-          return __webpack_require__(5769);
-        },
-      ]);
-      if (false) {
-      }
+      module.exports = __webpack_require__(5984);
 
       /***/
     },
 
-    /***/ 5769: /***/ (
+    /***/ 3043: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -33,7 +26,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(8293);
+        __webpack_require__(2227);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_script__WEBPACK_IMPORTED_MODULE_1__
@@ -66,12 +59,19 @@
       /***/
     },
 
-    /***/ 8293: /***/ (
-      module,
+    /***/ 3642: /***/ (
+      __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(900);
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/script",
+        function () {
+          return __webpack_require__(3043);
+        },
+      ]);
+      if (false) {
+      }
 
       /***/
     },
@@ -81,7 +81,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1984)
+      __webpack_exec__(3642)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>withRouter-HASH.js</strong></summary>

```diff
@@ -1,34 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [3263],
   {
-    /***/ 4631: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(7086);
-
-      /***/
-    },
-
-    /***/ 9216: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/withRouter",
-        function () {
-          return __webpack_require__(9803);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 9803: /***/ (
+    /***/ 1089: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -43,7 +16,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4631);
+        __webpack_require__(9413);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_router__WEBPACK_IMPORTED_MODULE_1__
@@ -61,13 +34,40 @@
 
       /***/
     },
+
+    /***/ 3962: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/withRouter",
+        function () {
+          return __webpack_require__(1089);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 9413: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(5282);
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9216)
+      __webpack_exec__(3962)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
```
</details>
<details>
<summary>Diff for <strong>1733-HASH.js</strong></summary>

Diff too large to display
</details>
<details>
<summary>Diff for <strong>4719-HASH.js</strong></summary>

Diff too large to display
</details>
<details>
<summary>Diff for <strong>main-HASH.js</strong></summary>

Diff too large to display
</details>
<details>
<summary>Diff for <strong>app-page-exp..ntime.dev.js</strong></summary>

```diff
failed to diff
```
</details>
<details>
<summary>Diff for <strong>app-page-tur..ntime.dev.js</strong></summary>

```diff
failed to diff
```
</details>
<details>
<summary>Diff for <strong>app-page-tur..ntime.dev.js</strong></summary>

```diff
failed to diff
```
</details>
<details>
<summary>Diff for <strong>app-page.runtime.dev.js</strong></summary>

```diff
failed to diff
```
</details>

</details>

</details>
Commit: 93a75e846158859cba6baf5b76f7740df6b88666
author_association: MEMBER
user: login: ijjk
created_at: 2025-05-21T13:07:36Z
body: ## Tests Passed

<!-- ## Failing test suites -- >
author_association: MEMBER