🌉 Turning an Idea into Reality: Finding Water Fountains in SF with Google Cloud 💧
Here’s a technical step-by-step guide for how I create websites from nothing.
Tech Stack
To give you a preview, here are some tools and languages I’ll choose:
Tools:
- Google Cloud: Web Hosting (Cloud Run)
- GitHub: Source control
- Cloudflare: CDN service
- Namecheap: Domain name registration
Languages:
- Next.js
- TypeScript
- Tailwind CSS
Steps
1. Register Domain with NameCheap
Think of a domain name for your site. It may take many searches to find an available domain.
It’s pretty simple. For example here’s a good option…
Once you check out, you have your domain!
—
To hook up to your future site, in the domain nameserver settings, select Custom DNS and add your two Cloudflare URLs:
Note that these DNS server records may take up to 48 hours to take effect.
(We may change these nameservers later when we setup Cloudflare.)
2. Create GitHub Repo
Create a new GitHub repo to store your source code. Clone the repo.
Initialize a React TS app:
npx create-next-app@latest
Feel free to modify the code as you’d like. I like to move my files to the top-level of the git repo.
Use npm run dev
to preview your frontend site!
Now before modifying the site, let’s git commit
and git push
to GitHub and do a bit more work to deploy it.
3. Create Google Cloud Project
We’ll use Google Cloud for hosting the Node.js server that will serve our application.
Create a new project on Google Cloud at https://console.cloud.google.com:
Next, let’s setup our terminal with gcloud
:
brew install --cask google-cloud-sdk
gcloud auth login
gcloud config set project watersfcom
Enable billing if not already enabled (you’ll very likely be within the free tier). You can check if billing is enabled with this command:
gcloud billing projects describe watersfcom
Enable services that will be used for this app:
gcloud services enable artifactregistry.googleapis.com cloudbuild.googleapis.com run.googleapis.com
Create a deploy.sh
file in your repo with the following contents:
#!/bin/bash
SERVICE=""
if [[ "${RUN_ENV}" == "PROD" ]]; then
# Disable update check to avoid prompt
gcloud config set component_manager/disable_update_check true
SERVICE="watersf"
else
# Default to staging
SERVICE="watersf-staging"
fi
echo "Deploying to project: ${SERVICE}"
# Build the app
npm run build;
buildStatus=$?
if [[ $buildStatus -eq 0 ]]; then
echo "!!! Build successful !!!"
# Deploys the web app to Cloud Run
gcloud run deploy $SERVICE \
--project watersfcom \
--region us-central1 \
--source . \
--allow-unauthenticated;
else
echo "!!! Build failed. Fix build then redeploy !!!"
fi
I’m using a package.json
config like such:
{
"name": "watersf-www",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"stage": "./deploy.sh",
"deploy": "RUN_ENV=PROD ./deploy.sh"
},
"dependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^7.10.0",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"eslint-plugin-react-refresh": "^0.4.7",
"next": "14.2.3",
"postcss": "^8",
"react": "^18",
"react-dom": "^18",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
Try deploying with npm run deploy
(takes a minute). Example logs:
~/Documents/github/grant/watersf git:(main) npm run deploy
> watersf-www@0.1.0 deploy
> ./deploy.sh
Deploying to project: watersf-staging
> watersf-www@0.1.0 build
> next build
▲ Next.js 14.2.3
Creating an optimized production build ...
✓ Compiled successfully
Linting and checking validity of types .
⚠ The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/basic-features/eslint#migrating-existing-config
./src/app/layout.tsx
7:14 Warning: Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components. react-refresh/only-export-components
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (5/5)
✓ Collecting build traces
✓ Finalizing page optimization
Route (app) Size First Load JS
┌ ○ / 5.29 kB 92.2 kB
└ ○ /_not-found 871 B 87.8 kB
+ First Load JS shared by all 87 kB
├ chunks/23-0627c91053ca9399.js 31.5 kB
├ chunks/fd9d1056-2821b0f0cabcd8bd.js 53.6 kB
└ other shared chunks (total) 1.86 kB
○ (Static) prerendered as static content
!!! Build successful !!!
This command is equivalent to running `gcloud builds submit --pack image=[IMAGE] .` and `gcloud run deploy watersf-staging --image [IMAGE]`
Building using Buildpacks and deploying container to Cloud Run service [watersf-staging] in project [watersfcom] region [us-central1]
✓ Building and deploying... Done.
✓ Uploading sources...
✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds/b781d801-557d-4266-bd5e-e965aaf56827?project=166145790296].
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [watersf-staging] revision [watersf-staging-00007-fbr] has been deployed and is serving 100 percent of traffic.
Service URL: https://watersf-staging-fjxvwo57jq-uc.a.run.app
One question you might have is: “How does Cloud Run know how to run this Node application?”. We didn’t even provide a Dockerfile!
Well, that’s where Cloud Run’s integration with Google Cloud Buildpacks comes in! If you run gcloud run deploy
with --source
, the command will first upload our source code to Cloud Build and build the container. Buildpacks provides a default builder (gcr.io/buildpacks/builder:latest
) that automatically detects the runtime (Node.js) and installs the necessary libraries (npm and Node.js), runs npm run build
, and saves the container.
You can learn more about GCP buildpacks here and the Node.js builder here.
The logs are pretty useful for understanding what is technically going on!
At the bottom you can see your service at a run.app
URL.
4. Setup Cloudflare
For a CDN, we can use Cloudflare for free. This service will act as the doorstep to any requests made to our site.
On cloudflare.com, create a site:
Scroll down and choose the free option at the bottom of the pricing page:
We’ll come back to this later.
5. Cloud Run Domain Mapping
To get our service URL https://watersf-staging-fjxvwo57jq-uc.a.run.app to something more usable like watersf.com, we can use Cloud Run’s nifty domain mapping feature.
Let’s use the command line for extra convenience. First log your current domains:
gcloud domains list-user-verified
Now you’ll need to add your domain you registered to this list.
- Go to https://search.google.com/search-console
- Click “Add property” and paste in the domain (like
watersf.com
). You’ll receive a string needed to add to your DNS records. - Create a TXT record in Cloudflare:
TXT | watersf.com | google-site-verification=ZM_V3bw_QhSys31bV2ot_C2Q_uS_iO_pXCNls17UshM
- Click “Verify” in the Google Search Console.
- Verify you completed this step with
gcloud domains list-user-verified
Add the domain mapping to Cloud Run with gcloud.
You’ll receive a list of DNS records to update:
gcloud beta run domain-mappings create --service watersf --domain watersf.com
...
Creating......done.
Waiting for certificate provisioning. You must configure your DNS records for certificate issuance to begin.
NAME RECORD TYPE CONTENTS
watersf A 216.239.32.21
watersf A 216.239.34.21
watersf A 216.239.36.21
watersf A 216.239.38.21
watersf AAAA 2001:4860:4802:32::15
watersf AAAA 2001:4860:4802:34::15
watersf AAAA 2001:4860:4802:36::15
watersf AAAA 2001:4860:4802:38::15
For me, this Cloudflare configuration looks like this:
Important: Use “@” for the “name” field to indicate root / watersf.com.
Additionally, remember those Cloudflare Nameservers we setup in NameCheap awhile ago? If you scroll down a bit on the DNS management page, you’ll see the servers we’ll want to configure for Namecheap:
6. Design the Frontend
All of the above was really just boring setup for scaffolding the actual website. Now for the fun stuff! Let’s create our application.
What is watersf.com? Let’s take the pattern scenario — solution:
Scenario: I am running in San Francisco and I want to find the closest free water fountain near me. Or the nearest restroom.
Solution: I open the watersf app on my phone and press a button to navigate me to the nearest water fountain. In one click!
How?
Well, the SF gov publishes data around public water fountains and restrooms:
Here’s the data:
We can create an app with 1 button that gets your location and routes you to the nearest water fountain via Google Maps. Or restroom.
There’s even a JSON API: https://data.sfgov.org/resource/wfq4-upmv.json
So… we have the data. We can request the user’s GPS and compare against the data to find restrooms sorted by Euclidean distance (descending). Show those as the results with a link.
What should the UI look like?
Let’s use one of these AI apps be our designer…
Design Requirements
There are two main pages:
HOME PAGE: Show a button for finding water fountain.
RESULT PAGE: A list of results with map links to water fountains.
I used an image generator to create inspiration:
The second image with a big button “FIND WATER” on the home page sounds good!
Check it out yourself at watersf.com!
On Chrome mobile, you can use the “Add to Homescreen” button to get a quick link. It looks like this for me:
Thanks for reading!
By the way, all of this code is open source here:
https://github.com/grant/watersf
If you enjoyed this, be sure to give a clap 👏 or write a comment ✏️.