The VCS has some handy features that makes it easier.
First, there is no frame buffer (there could be no such thing in that memory space), but there are effectively line buffers for the different sprite types and background. These are not in the 128 bytes of general purpose RAM but memory mapped to the display controller.
Second, it maintains the patterns in these buffers until you change them and will repeat them on the next line unless you do anything to change them. This makes it easy to perform partial update, e.g. only update the background every 8 lines and only update sprite graphics on odd lines or whatever you need to do to save time.
Third, the video controller can be instructed (via a memory mapped register) to halt the CPU until the next horizontal "blanking" interval. This makes synchronizing to the beam position between lines much easier.
Fourth, there are no interrupts (except NMI, only triggered manually with the brk instruction) so timing is easily predictable.
Fifth, there is a hardware timer which makes it easier to keep track of your budget while in the vertical blanking portion of the screen, where you probably want to handle most if not all of your game logic. That way the game logic doesn't need to be strictly timed in the same way the screen is.
I made a game for it (https://text.garden/jupitersumo.html) expecting it to be difficult, but found that the simplicity and predictable timing made it surprisingly easy. On the C64 for example you will often find yourself wanting to synchronize to the beginning of a line or even every line for one reason or another, but doing that while dealing with the complexities of its timing is a whole science unto itself. For the main game screen I ended up using some 16 bytes of state in RAM to represent the stuff I need to keep track of while "racing the beam". The code that handles the display is maybe 150 instructions long and lives in the game ROM. The source code is linked from the page if you're interested in how it works.
Of course, in terms of what it displays, my game is very simple, only showing a background and the player sprites. There are much more complex display routines ("kernels") in the VCS game library. An interesting example is Pitfall II, which is very impressive considering the platform, but uses a coprocessor on the cartridge itself (called the DPC) to help sequence line register updates. From memory, I think it lets you enqueue data in advance, and during the display routine the CPU can just read a single register to get the next byte from a queue.