Thank you for your reply. I found a workaround on my own, so the below is provided for your developers in case they wish to investigate further.
When Breeze is enabled, the CRLF gets inserted into the output buffer. When Breeze is disabled, the character disappears, so it seems reasonable to assume it is something Breeze is doing, though I’m not sure what that would be. In my experience this type of thing is usually caused by a blank line left outside of the PHP brackets in a place where it shouldn’t be, but I scanned the plugin code and didn’t find anything like that – nothing obvious anyway. I also tried other cache plugins to see if it is just cache in general, but they did not produce the same behavior.
There are no related PHP errors as it isn’t really an error at all – just a malformed output buffer. I’ll provide my Breeze settings below, but again, it seems to inject the character no matter what settings we use – even with all features disabled. I tried many combinations of settings, and even tried adding the page in question to the Excluded URLs list at one point.
Here’s my workaround which forcefully removes the stray character from the output buffer.
// save output buffer to a variable so we can manipulate it
ob_end_clean();
ob_start();
$objWriter->save('php://output');
$content = ob_get_contents();
ob_end_clean();
// get rid of stray CRLF
$content = ltrim($content, "\r\n");
echo $content;
Breeze settings (api-token removed):
{"breeze_basic_settings":{"breeze-active":"1","breeze-mobile-separate":"0","breeze-cross-origin":"0","breeze-disable-admin":{"administrator":0,"author":0,"contributor":0,"editor":0,"subscriber":0,"wpseo_manager":0,"wpseo_editor":0},"breeze-gzip-compression":"1","breeze-browser-cache":"1","breeze-lazy-load":"0","breeze-lazy-load-native":"0","breeze-lazy-load-iframes":"0","breeze-lazy-load-videos":"0","breeze-desktop-cache":"1","breeze-mobile-cache":"1","breeze-display-clean":"1","breeze-b-ttl":1440},"breeze_advanced_settings":{"breeze-exclude-urls":[],"cached-query-strings":[],"breeze-wp-emoji":"0","breeze-store-googlefonts-locally":"0","breeze-store-googleanalytics-locally":"0","breeze-store-facebookpixel-locally":"0","breeze-store-gravatars-locally":"0","breeze-enable-api":"0","breeze-secure-api":"0","breeze-api-token":"******"},"breeze_heartbeat_settings":{"breeze-control-heartbeat":"0","breeze-heartbeat-front":"","breeze-heartbeat-postedit":"","breeze-heartbeat-backend":""},"breeze_cdn_integration":{"cdn-active":"0","cdn-url":"","cdn-content":["wp-includes","wp-content"],"cdn-exclude-content":[".php"],"cdn-relative-path":"1"},"breeze_varnish_cache":{"auto-purge-varnish":"1","breeze-varnish-server-ip":"127.0.0.1"},"breeze_first_install":"no","breeze_preload_settings":{"breeze-preload-fonts":[],"breeze-preload-links":"1","breeze-prefetch-urls":[]},"breeze_file_settings":{"breeze-exclude-urls":[],"cached-query-strings":[],"breeze-wp-emoji":"0","breeze-store-googlefonts-locally":"0","breeze-store-googleanalytics-locally":"0","breeze-store-facebookpixel-locally":"0","breeze-store-gravatars-locally":"0","breeze-enable-api":"0","breeze-secure-api":"0","breeze-api-token":"******"},"breeze_ecommerce_detect":false,"breeze_advanced_settings_120":"yes"}