Josh Comeau's comment:
We've added an action and method to our form.
For folks who have JS enabled, these attributes have no effect. We stop all of the browser's typical behaviour with event.preventDefault(), and we hit the API endpoint using fetch.
However, for folks who have JS disabled, the browser will make an HTTP request to the URL specified in action, which is /api/search.
So, in either case, a request is being made to the same URL. But there's a critical difference:
When JS is enabled, the request will have a Content-Type header equal to application/json. This tells our backend API endpoint that it should respond with a JSON object, which will be used by our client-side code to update the React state. When JS is disabled, the Content-Type header will be set to application/x-www-form-urlencoded. This is the default content type when submitting a form with POST. It tells our backend API endpoint to respond with HTML instead of JSON. Our backend API route can read this value from req.headers['Content-Type'], and decide how to respond.
The exact implementation will depend on your technology. If you're using something like Next.js, you can redirect to a search results page, adding the search term in a query parameter. If you have a hand-rolled server-side rendering approach, you can generate the HTML file with ReactDOMServer.renderToString.