Skip to main content
← Blog

Deploying Fecit for the web

VauDium ·

Server setup, proxies, and the Apple/Google login struggles of bringing a mobile app to the web.

Deploying Fecit for the web

Fecit started as a mobile app. iOS and Android. But I wanted to use it on desktop too. So I built a web version.

fecit.vaudium.net. Scaffolded with Vite + React + TypeScript, reusing as much mobile logic as possible while adapting for the web. React Router v7, Jotai, Tailwind CSS v4, i18next. The shared palette and locale files from mobile were imported directly to keep design and translations consistent.

Building the app wasn’t hard. Deploying it was.

Separate servers and proxying

The API server was already running. The web app needed its own server. I used httpd (Apache2) installed via brew.

Why Apache? It was already on the server, and I was familiar with the config. Nginx might be better, but there was no reason to switch at this stage.

When the web app calls the API, you hit CORS issues — different domains. The fix was setting up a proxy in httpd. /api/* requests get forwarded to the API server; everything else serves the web app’s static files.

This part went smoothly.

Apple Login — the localhost trap

When implementing Apple login, I put localhost as the redirect URI for local development.

It doesn’t work.

Apple doesn’t allow localhost in redirect URIs. I didn’t know this. The error messages weren’t helpful, so I spent a long time debugging. Eventually I had to test against the real domain.

On mobile, the Apple Sign In SDK handles all of this transparently. On the web, doing OAuth directly, these restrictions surface.

Google Login — access_token vs ID Token

Google login was a different kind of struggle.

I started with useGoogleLogin. It returns an access_token. But the server expects an ID Token (credential). On mobile, Google Sign-In SDK gives you the ID Token directly. On web, useGoogleLogin gives an access_token. Different format.

So I switched to the GoogleLogin component. This one returns a credential (ID Token). Problem solved… or so I thought. This time, the button couldn’t be customized. You’re stuck with Google’s default button.

The final solution was calling google.accounts.id.prompt() directly. Custom button preserved, ID Token received. It took three commits to get there.

Looking back

Web deployment had more unexpected friction than app development. I spent more time on configuration than on code.

  • Server setup: httpd proxy config is set-and-forget, but the first setup had its share of trial and error
  • Apple login: If I’d known about the localhost restriction upfront, a 30-minute task wouldn’t have taken half a day
  • Google login: Understanding the difference between access_token and ID Token took time

Mobile SDKs hide so much complexity. Building the same flows directly on the web made me appreciate that — and gave me a much deeper understanding of OAuth.

fecit.vaudium.net is running well now.