C hasn't seen a lot of relevant changes since C89 as far as I'm concerned. The most important for me is definitely declare-anywhere (which had been widely supported prior to standardization in C99), it removes most of the friction when quickly trying out new stuff.

Here are some things I almost always do to improve ergonomics (some of this is debatable and none of this should be exposed in a library, at least prefix things)

// common.h

#pragma once

#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

#define ARRAY_COUNT(a) ((int) (sizeof (a) / sizeof (a)[0]))
#define STRUCT(name) typedef struct name name; struct name
#define CONTAINER_OF(ptr, type, member) ((type *) ((char *) (ptr) - offsetof(type, member)))
#define ALIGN(n) __declspec(align(n))
#define CACHE_ALIGN ALIGN(64) //XXX

typedef uint8_t u8;
typedef uint16_t u16;
//...

Each .c file includes "common.h" as the first thing.
Each .h file has #pragma once at the beginning.

ARRAY_COUNT(a) is used to get the capacity of a C array without using defines (which is brittle). Unfortunately it's imperfect because code breaks when you change arrays to dynamically allocated buffers and you forget to switch to using dynamic capacity values. This is one case where I'd like to see some standards update that allows us to improve safety.

STRUCT(x) is used to declare struct x with typedef -- I've grown to hate tag namespaces for their boilerplate. I simply uppercase types, and the problem (that struct tags were invented to solve) is gone.

Probably I'll make something like this very soon to get started

static void *__xmalloc(size_t size);
static inline void __xmalloc_array(size_t size, size_t count) { /* ... */ }

#define xmalloc(type) ((type *) __xmalloc(sizeof (type)))
#define xmalloc_array(type, count) ((type *) __xmalloc_array(sizeof (type), (count)))

Later obviously more sophisticated allocation is needed, but the point here is how macros can be employed to improve safety and ergonomics. This is how I do polymorphism in C basically, not much more is needed than abstracting over size and maybe alignment for almost everything. In some cases, manually set up v-tables make sense from an architectural perspective.

I also often make a very simple "logging" module that does basically printf logging but with \n automatically added and optionally printing out __FILE__ and __LINE__ but without requiring to re-type this all the time. Something like

void __msgf(const char *file, int line, const char *fmt, ...)
{
va_list ap; va_start(ap, fmt);
if (__debug) printf("At %s.%d: ", file, line);
vprintf(fmt, ap);
printf("\n");
va_end(ap);
}
#define msgf(...) __msgf(__FILE__, __LINE__, __VA_ARGS__)

Other than that, I like to not think about the language but about what the machine is going to do. How to decrease size of working set and generally speed up the program. How to speed up compilation (don't expose all the internals in the .h files). Things like that.

There are very few container data structures actually needed, most of the time it's just C-arrays, dynamically allocated buffers (pointer + capacity), and some simple queues / synchronization primitives. Also linked lists. A favorite of mine are chunk-lists. All of these are very simple to implement -- there is hardly a point of making them in a super-general library, it's ok to code them from scratch for all but the smallest projects.