To do this without changing the block’s hash, transactions are hashed in a Merkle Tree, and only the root is included in the block’s hash.
This allows old blocks to be compacted by cutting off branches of the tree, and the interior hashes don’t need to be stored.
A block header with no transactions is only about 80 bytes, and with blocks generated every 10 minutes, the yearly storage needed for block headers is not a problem even with the growth of computer systems.