A typical single-page app is served out of a single index file, where the routing of the request is done client-side; in other words, the single-page app (the index file) is loaded by the browser, its immediate JS, CSS, image, and other resources are loaded, and then the client-side JavaScript will route the request based on the URL and make subsequent requests to populate the page.
Serving SPAs is easy with Caddy. Basically, if the requested file does not exist (and static resources such as CSS, JS, and images do exist), then rewrite the request internally so that the index file is served:
try_files {path} /
file_server
You may have to adjust this for your specific use case, but this is the basic idea.