How do I move the page title (H1) to header.php (outside of the loop) in a WordPress theme?

I know I can do a bunch of if statements and just make sure I cover
every scenario, but that seems messy, surely there’s a cleaner and
more future proof option?

Nope! It is a bit messy. Unfortunately there’s no single function for outputting a title for all page types.

There’s essentially 4 ‘types’ of pages in WordPress that will need different titles:

  • Single posts (or pages)
  • Archives
  • Search results
  • 404

So you could write your own function that you could put in header.php that would output an appropriate title for each type of page:

function wpse_321605_title() {
    if ( is_singular() ) {
        $queried_object_id = get_queried_object_id();

        echo get_the_title( $queried_object_id );
    } else if ( is_archive() ) {
        the_archive_title();
    } else if ( is_search() ) {
        echo 'Searching for: ' . esc_html( get_search_query() );
    } else if ( is_404() ) {
        echo 'Page Not Found';
    }
}

However, a lot of people don’t like that the_archive_title() prefixes category and tag archives with “Category:” and “Tag:”, so if you don’t want those prefixes you’ll need to handle taxonomy archives separately and use single_term_title():

function wpse_321605_title() {
    $queried_object_id = get_queried_object_id();

    if ( is_singular() ) {
        echo get_the_title( $queried_object_id );
    } else if ( is_tax() || is_tag() || is_category() ) {
        single_term_title()
    } else if ( is_archive() ) {
        the_archive_title();
    } else if ( is_search() ) {
        echo 'Searching for: ' . esc_html( get_search_query() );
    } else if ( is_404() ) {
        echo 'Page Not Found';
    }
}