My usual trick to a) flush as required but b) not flush on every page load is to use an option to determine if the rewrites have changed, and flush only if they have indeed changed.
Something like this:
add_action( 'wp', 'wpse_421338_maybe_flush_rewrites' );
function wpse_421388_maybe_flush_rewrites() {
$rewrite_obj = new WP_Rewrite();
$rewrites = $rewrite_obj->wp_rewrite_rules();
// Compare the current state of the rewrites to the last stored state.
$current_state = md5( json_encode( $rewrites ) );
$stored_state = get_option( 'wpse_421338_rewrite_state', '' );
if ( $current_state !== $stored_state ) {
// If things have changed, flush the rewrites...
flush_rewrite_rules();
// ...and store the new state.
update_option( 'wpse_421338_rewrite_state', $current_state );
}
}
…might work.
Note that this code is untested and should not be used in production until you’re sure it works.