it seems like FPGA might be the only practical alternative to a microcontroller
"Only practical alternative" - what you're doing is hardly "practical". If you wanted practical on a Z80 (for whatever reason), you'd use a modern eZ80. It has a built-in SPI peripheral :) Some years ago you'd have also had the option of using a Rabbit MCU, but those IIRC are gone now.
Alas, I'm sure it's all done just for the heck of it and "why not". Several period-accurate retro options are available for you.
Bit-Banging
"Bit banging" on a CPU like Z80 would be looked down upon, because that poor CPU is not very fast to begin with, so to waste the precious cycles on major bit-banging likely wouldn't be practical - at least not if application speed was a concern, e.g. when using CP/M and some perhipheral via SPI.
UART IC
Perhaps you don't care to repurpose an existing synchronous USART, 'cuz it'd be almost like cheating.
Discrete UART
At minimum you'd do an actual I/O-mapped SPI "UART". Basic options are:
Use a handful of TTL or 4000-series logic.
Use a couple PALs - likely two registered ones and a combinatorial one. Or use three GALs.
The "handful of TTLs/4000s" would be a very likely route to go in the late 70s and early 80s. You need a shift register, maybe a couple octal latches for a FIFO if you're so inclined - then also two or three counters to keep state, a clock divider, a bit counter, and some combinatorial logic to make it all work. Without a FIFO you'd want to use good old Z80DMA to keep the data moving.
I believe the ability to solve everything with code is hindering my ability to learn more optimal hardware solutions.
MCUs usually come with SPI peripherals, so you're not solving the SPI problem with code. You're just using code to talk to the SPI peripheral that then talks to the LoRa. Since legacy Z80 has no built-in SPI support, you'd provide it yourself, but it'd be a very application-agnostic thing - just like a UART, except for SPI, not caring much what the data sent/received actually means.
Bus-mapped SPI Peripheral Register Space
Now, if you don't want to use software to deal with SPI transactions, and want a "higher level" solution that would feel more native - and perform better - you'd want to expose the SPI slave's register space directly to I/O port space of the Z80.
To do that, you'd put together a state machine that converts I/O accesses to SPI transactions, and adds any necessary wait states to the bus transactions. SPI on most peripherals runs quite fast compared to legacy Z80 bus speeds, so perhaps no waitstates would be needed - or just one. The software would see the SPI periphera's register space as-if it was a parallel data bus peripheral with address decoding, connected to the bus. So, the LoRa chip would appear more like a UART to the software.
That'd be quite a nifty solution to the problem, and not too impractical with modern design tools. You can design a much tighter through-hole 2-layer PCB today than in the 80s - and get it manufactured for coffee money. You can even (OMG!) have several prototypes made over a period of weeks, iterating quickly to get all the bodge wires off the board. In the 80s and 90s, you'd have needed deep pockets to afford that, and ideally a PCB manufacturer in the same town.
The "software" the Z80 would need to use this peripheral would probably fit in 128-256 bytes of EPROM. So, very period-appropriate for late 70s. Heck, you probably could fit it in the 74-series bipolar PROMs if you can find some, and someone to program them for you.
Discrete State Machine + "UART"
Once you're ready to ditch the Z80, set up another hardwired logic state machine that will use the bus-mapped SPI "adapter" to provide the equivalent of the application layer.
SPI Playback from Memory
Or, you know, you can just play back bits from a parallel EPROM or PROM when the trigger comes. D0 for MOSI, D1 for SCLK, D2 for ~CS. An external address counter, and an RS FF to stop the counter when it overflows. But that feels almost like cheating: back then memory was expensive, and a CPU with a small PROM with code, plus some discrete glue logic, may have cost less than a large PROM with bit-banged messages.
SPI Playback from Less Memory
MC14500B would be considered a slightly more integrated state-machine building block. A couple ancillary chips and a small (64 byte?) PROM would do it.
SPI Playback with Slightly More Software
An Intel 4004 + one or two 4001s could be used to "approximate" a discrete state machine. Given how simple those chips were, it really isn't anything even remotely like writing Z80 assembly, in terms of the abstraction level. A 4-bit CPU is plenty for bit-banging a fixed short SPI message or a few. 4001s give you some I/O and code/data ROM. A 4004 provides a slightly higher level assembly than MC14500B family, but still close to writing "state machine assembly".