You don't necessarily need a custom webpack setup. Just dump the production distribution produced by your build tools into the web2py /static folder and serve from there (actually, you can serve the static assets from anywhere, as long as you configure your web server properly).
One thing to consider is how you are handling client-side routing. If you are using the history API in the browser, then when a request is made for a client-side route, web2py will need to distinguish that from a server-side route and return the React index.html static page. Again, you can configure your web server to handle this -- it will need to know which URLs to route to web2py, which are React static assets, and then assume all other routes are client routes and simply return the index.html page in that case. That's probably the most efficient option, but you could also have web2py handle it. First, add something like this in the first model file:
if request.controller == 'default' and not (request.is_shell or request.is_scheduler):
request.function = 'index'
# Skip the remaining models, as we are only serving static HTML.
response.models_to_run = []
The idea is to have only an index() function in the default.py controller, and use that to serve the static index.html file. Put all of you web2py actions in other controllers, and make sure none of your client routes start with a path segment that matches any of your web2py controllers. When web2py receives a request for a client route, because the first segment of the path will not match any controllers, it will assume the default.py controller (you should set up routes.py with 'default' as the default controller in the router). The above code catches that case and then sets the function to 'index' to force the return of the index.html file (it also skips running the remaining model files, as they are not necessary when simply returning the static index.html file).
In the default.py controller, then do something like this:
import os
def index():
response.view = os.path.join('..', 'static', 'index.html')
return dict()
The above simply sets the index.html file in the /static folder as the view for this function, so web2py ultimately returns index.html as the response (presumably it is not actually a web2py view with any Python template code, but web2py will still execute and return it -- and you could include some web2py template code if needed). You could use the @cache decorator to cache the output of this function for some time to speed things up.
Anthony