There are actually different solutions for this problem. Simple ones and very sophisticated stuff
Maybe the easiest approach is presented here: Endless Forest (2018 Port)
It’s some sort of showcase that demonstrates the basic structure of an endless runner. If you have a look at the source code, you will encounter a
run() function which responsible for the animation of the camera and particles but also for respawning objects in the virtual world. The ground actually consists of two separate objects which are changed at certain conditions. You might want to use a similar approach in your application.
This approach is definitely faster than manipulating vertices per frame. The problem is that your chunks have to match at the seams. Unlike in the forest demo, your terrain is not flat which makes this a bit more complicated. In general, you can achieve this by generating the entire terrain geometry and divide it into multiple sections. This can be done at application start or in advance with a tool. You can then render only the current section and the adjacent ones. A section would be represented in
three.js as a separate mesh with its own geometry. If you want to speed up things a bit, you can try to use a single mesh by merging the current geometry data via BufferGeometryUtils.mergeBufferGeometries(). Or you give instanced rendering a try.
It depends on your particular use case if this approach is sufficient or if you need something smarter.
An advanced terrain rendering algorithm was presented by Florian Bösch in the book WebGL Insights. The related chapter is Terrain Geometry - LOD Adapting Concentric Rings. He presented a LOD technique which is very useful in context of terrain rendering since you want to show more details at the players position and less details at distant places. Maybe you can use some of his thoughts for your own application.