103 Early Hints for WordPress

I’m wanting to add scripts & styles to the Link header so that Cloudflare can cache & serve the HTTP 103 Early Hints.

I’m not too experienced with WordPress but I am good with PHP so share any suggestions or pre-made plugins/code you can give no matter how technical.

I’m hoping that somebody out there already has a ready made solution that will automatically get the enqueued scripts & styles and just output them into the Link header. Any footer enqueued scripts & styles should not be output to the Link header.

Is using the WP enqueued head scripts & styles a good idea? I’m thinking this would be the best source as its dynamic per page and will be managed automatically as they add/remove plugins and get updates.

Keep in mind NGINX / Apache max header size 4K, 8K.
Can’t use APO or Workers.
The site is on Pro plan.

2 Likes

Nobody answered with a ready made solution so I write one.
Theres a few points of concern here and I wouldn’t recommend running in production yet.

Regarding the “relevant page” check on line 20:
I’m not too familiar with WordPress so does anyone know how to determine if its a page without checking the URI? I’d like this mu-plugin to output Link headers on all pages, so not AJAX or anything else.
I think there should be some way to get a post type and use that to whitelist which post type it can run on.

Regarding the ob buffer end flush on line 111:
This isn’t a good solution, if the header limit is never reached then it won’t end the ob until PHP finishes execution which could introduce too long delay on the TTFB. Anyone know another WordPress hook I can run directly after the script_loader_tag and style_loader_tag which I can call ob_end_flush() at?


/**
 * SEND HTTP LINK HEADERS
 * Link headers will be cached and delivered as 103 Early Hints by Cloudflare
 */
function startup_check_and_buffer_for_link_headers( $nothing = '' ) {

    // FIRST CHECK IF HEADERS ARE ALREADY SENT
    if (headers_sent()) return;


    // CHECK IF THIS IS PAGE POST :: Not applicable to ajax
    if (!isset($_SERVER['HTTP_HOST'])) return;
    if (!isset($_SERVER['REQUEST_URI'])) return;


    // CHECK IF THIS IS A RELEVANT PAGE
    $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    if (
        $uri !== '/'
        && strpos($uri, 'product/') !== 1
        && strpos($uri, 'product-category/') !== 1
        && strpos($uri, 'cart/') !== 1
        && strpos($uri, 'checkout/') !== 1
        && strpos($uri, 'contact-us/') !== 1
    ) {
        header('X-LINK-DIAG-REASON: URI Not Matched '. $uri);
        return;
    }


    // ADD FILTER 
    add_filter('script_loader_tag', 'set_link_header_from_enqueued_head_script', 10, 3);
    add_filter('style_loader_tag', 'set_link_header_from_enqueued_head_style', 10, 3);


    // START AN OUTPUT BUFFER SO NO HEADERS ARE SENT YET
    ob_start();


    // STARTUP PASS
    return;
}
add_action('wp_headers', 'startup_check_and_buffer_for_link_headers');



/**
 * Get Link HTTP Headers Length
 * This function is uses to count the string length of the HTTP Link headers sent.
 * @see set_link_header_from_enqueued_in_head which utilises this function
 */
function get_link_http_headers_length() {
    $count = 0;

    // ADD UP THE LENGTH FOR EACH LINK HEADER :: Minus the "Link" because apache will combine.
    foreach (headers_list() as $x)
        if (strpos($x, 'Link:') === 0)
            $count += strlen($x) - 4;

    // RETURN THE COUNT MINUS 1: Subtract 2 becasue the first doesn't need , seperator.
    return $count - 1;
}



/**
 * Header length cannot be longer than 4KB due to lowest sped on NGINX.
 * There are also other plugins and WP itself which adds a LINK value so we must
 * allow for some headroom of about 1024 so the max we will use here is 3072.
 * Apache or NGINX will combine the Link headers to comma separated values.
 */
function set_link_header_from_enqueued_in_head( $handle, $src, $type ) {

    // RETURN IF INVALID HANDLER OR SRC
    if (!$handle || !$src) return;


    // EXCLUDED SCRIPTS (by handle)
    $excluded = ['admin-bar'];
    if (in_array($handle, $excluded, TRUE)) return;


    // PARSE THE URL SO WE CAN SIMPLIFY IT :: Merge to ensure all props we'll use are set.
    $src_link = esc_url($src);
    $parse_src = array_merge([
        'host' => '',
        'path' => '',
        'query' => '',
        'fragment' => ''
    ], parse_url($src_link));


    // CHECK IF CAN SIMPLIFY
    if ($parse_src['host'] === $_SERVER['HTTP_HOST'])
        $src_link = $parse_src['path'] . ($parse_src['query'] ? '?' : '') . $parse_src['query'];


    // DONT INCLUDE IF HAS FRAGMENT
    if ($parse_src['fragment']) return;


    // CHECK LENGTH ISN'T TOO LONG
    $push_condidate = "<$src_link>; rel=preload; as=$type";
    $new_length = strlen($push_condidate) + get_link_http_headers_length();


    // IF TOO LONG, THEN IMMEDIATELY END THE OB AND THAT'LL SEND THE HEADERS ASSUMING NO OTHER OB
    if ($new_length > 3072)
        ob_end_flush();


    // ELSE :: ADD THE LINK HEADER :: Don't implode array, don't override header. Instead preserve push header.
    else header("Link: $push_condidate", FALSE);


    // DONE
    return;
}



/**
 * Set Link Header From Enqueued Head Script
 * This function is called each time an enqueued head script is output to the
 * <head> by WordPress action. Its called by the script_loader_tag wp filter.
 */
function set_link_header_from_enqueued_head_script( $tag, $handle, $src ) {

    // IF HEADERS ALREADY SENT THEN ABORT
    if (headers_sent()) return $tag;

    // CALL FUNCTION TO INSERT IT
    set_link_header_from_enqueued_in_head( $handle, $src, 'script' );

    // DONE, RETURN THE TAG UNMODIFIED
    return $tag;
}



/**
 * Set Link Header From Enqueued Head Style
 * This function is called each time an enqueued head style is output to the
 * <head/> by WordPress action. It's called by the style_loader_tag wp filter.
 * @see startup_check_and_buffer_for_link_headers where it is add by filter.
 */
function set_link_header_from_enqueued_head_style( $tag, $handle, $href ) {
    
    // IF HEADERS ALREADY SENT THEN ABORT
    if (headers_sent()) return $tag;

    // CALL FUNCTION TO INSERT IT
    set_link_header_from_enqueued_in_head( $handle, $href, 'style' );

    // DONE, RETURN THE TAG UNMODIFIED
    return $tag;
}
2 Likes

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.