●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Implementation Notes: Building a Personal Blog Operations Dashboard with Streamlit and the Gemini API
Notes from building a single-pane operations dashboard that unifies Google Search Console and GA4 data with Gemini 2.5 Flash-powered quality scoring, after burning out on switching between 12 browser tabs across six sites every morning. Includes the full Streamlit implementation and weekly low-quality detection job.
For roughly six months, my morning routine was: open Google Search Console, click through four Lab sites and two blog sites, then repeat the whole tour in GA4. An hour would vanish before I had written a single sentence of new content or made a single piece of art. Eventually it became obvious that this had to be solved with software, not discipline.
I am Masaki Hirokawa. Alongside an art practice, I have been a solo iOS and Android developer since 2014 with roughly 50 million cumulative downloads. I currently run four Lab sites (Claude/Gemini/Antigravity/Rork) and two blog sites (Lacrima/Mystery) by myself. Lacrima drew 29,300 clicks over six months and Mystery drew 7,049 in the same window, both monetized via memberships and affiliate links.
These are implementation notes for the Streamlit-plus-Gemini-API dashboard that unifies GSC and GA4 onto one screen. The audience is indie publishers running multiple sites who are losing too much morning energy to dashboard hopping, and developers looking for a concrete, useful project to build with the Gemini API.
Why Watching GSC and GA4 Separately Drains You
GSC and GA4 both come from Google but give complementary views of the same reality. GSC shows "your site as seen from Google Search" (queries, impressions, clicks, position). GA4 shows "your site as seen by users in it" (users, sessions, engagement, conversion). Without both you cannot spot articles that rank well but engage poorly, or articles that rank poorly but earn through direct traffic.
Switching between tabs to compare them does not scale. One site is fine. Six sites means twelve tabs and re-setting the date range in every single one. In practice you stop doing it and only glance at today's numbers. Compound signals like "position dropped but engagement rose" become invisible, and decisions revert to vibes — which is roughly where I was six months ago.
Streamlit is a good fit because Python data-analysis skills translate directly into a web UI. A pandas frame becomes st.dataframe and st.line_chart, and a working dashboard lands in around 100 lines of code. The deployment story is just streamlit run app.py, which matches the indie operational budget.
Three points matter. (1) Join GSC and GA4 on page URL so the two views sit in the same dataframe. (2) Use Gemini 2.5 Flash for article-quality scoring — cheap enough for weekly site-wide scans, accurate enough for the Helpful Content axis. (3) Use Streamlit's caching to stay under GSC's daily rate limit — without it, switching between sites a few times a day will exhaust the quota.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Complete Streamlit dashboard code that unifies the Google Search Console API and the GA4 Data API into a single pane for indie publishers running multiple sites.
✦An article-quality scoring pipeline using Gemini 2.5 Flash, costing about $0.50/month for weekly full-site scans, with Helpful Content System-aligned deletion-candidate detection.
✦The KPI design and weekly auto-report patterns actually used to operate six sites in parallel — CTR, average position, engagement time, and premium conversion rate.
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
GSC API and GA4 Data API both authenticate with Google service accounts. For solo operations, juggling OAuth per account is a tax. Make one service account, grant it the right scopes on every GSC property and every GA4 property, done.
Store the service-account JSON key in Streamlit's secrets feature. With .streamlit/secrets.toml, the code reads st.secrets["service_account"] and there is no path that leads to accidentally committing the key.
GSC API returns one dimension grouping per request. Query-level and page-level data require two calls and a join.
# gsc.pyimport pandas as pdfrom datetime import date, timedeltadef fetch_gsc(client, site_url: str, days: int = 28) -> dict: end = date.today() - timedelta(days=2) # GSC lags ~2 days start = end - timedelta(days=days) body_query = { "startDate": start.isoformat(), "endDate": end.isoformat(), "dimensions": ["query"], "rowLimit": 5000, } body_page = {**body_query, "dimensions": ["page"]} queries = client.searchanalytics().query( siteUrl=site_url, body=body_query ).execute().get("rows", []) pages = client.searchanalytics().query( siteUrl=site_url, body=body_page ).execute().get("rows", []) df_q = pd.DataFrame([{ "query": r["keys"][0], "clicks": r["clicks"], "impressions": r["impressions"], "ctr": r["ctr"], "position": r["position"], } for r in queries]) df_p = pd.DataFrame([{ "page": r["keys"][0], "clicks": r["clicks"], "impressions": r["impressions"], "ctr": r["ctr"], "position": r["position"], } for r in pages]) return {"queries": df_q, "pages": df_p}
The end = date.today() - timedelta(days=2) margin is the small practical detail. GSC has one to two days of lag, so including today's date mixes in unconfirmed values that quietly distort your weekly comparisons.
GA4 Data Fetch — Per-Page Engagement
GA4 Data API uses the Reports API. Group by page path so it joins cleanly with the GSC page dimension.
The gotcha that cost me an afternoon was a GSC vs. GA4 page URL formatting mismatch. GSC returns full URLs (https://gemilab.net/ja/articles/...); GA4 returns relative paths (/ja/articles/...). Normalize before joining.
This is the centerpiece. Feed each article body to Gemini 2.5 Flash and have it score quality on a 0-100 scale calibrated to Helpful Content System concerns.
The choice of 2.5 Flash is cost-driven: $0.075 per million input tokens, $0.30 per million output. An 8,000-character article is roughly 3,000 tokens; scoring 100 articles costs about $0.03. Weekly full-site scans across six sites still stay under $0.50/month. Pro's accuracy is overkill for this task — the goal is to catch templated, padded articles, and Flash is calibrated well enough for that.
# quality.pyfrom google import genaifrom google.genai import typesPROMPT = """You are a quality reviewer well-versed in Google Search Console andthe Helpful Content System. Read the article and score quality from 0 to 100.Axes:- Originality (is this just a doc summary?): 30 pts- Practicality (can the reader take action after reading?): 30 pts- Tone (is it falling into templated AI explanation voice?): 20 pts- Structure (H2 organization, code examples, metrics balance): 20 ptsOutput JSON only:{ "score": 75, "uniqueness": 25, "practicality": 22, "tone": 16, "structure": 12, "deletion_candidate": false, "improvements": ["intro reads templated", "code lacks explanation"]}Set deletion_candidate to true if score < 60.---{article_body}"""def score_article(client, article_body: str) -> dict: response = client.models.generate_content( model="gemini-2.5-flash", contents=PROMPT.format(article_body=article_body[:30000]), config=types.GenerateContentConfig( response_mime_type="application/json", temperature=0.1, ), ) import json return json.loads(response.text)
Three lessons. (1) response_mime_type="application/json" removes nearly all parse failures.(2) temperature=0.1 keeps the score stable across rescans.(3) Truncating input at 30,000 characters is a deliberate cost cap — long articles' quality trends are detectable from the opening, and full-length scoring is rarely worth the spend.
Putting It Together in Streamlit — @st.cache_data Saves the Quota
Wire GSC, GA4, and Gemini scoring into a single Streamlit app. @st.cache_data with TTL prevents accidental quota burn when you flip between sites.
The ttl=3600 (1 hour) cache reflects GSC's daily call ceiling (1,200 requests per property per day). With multiple sites to switch between, uncached calls hit the cap fast.
Weekly Low-Quality Sweep
Beyond the dashboard, the operationally valuable thing is a weekly job that lists every article scoring under 60. Learning from a Helpful Content incident the hard way: low-quality articles drag down the whole site's quality assessment, so once detected they need to be removed (or expanded into legitimate premium content).
# weekly_scan.pyimport os, jsonfrom pathlib import Pathfrom quality import score_articlefrom google import genaidef scan_site(site_dir: Path, output: Path): client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) candidates = [] for mdx in site_dir.rglob("*.mdx"): body = mdx.read_text(encoding="utf-8") try: score = score_article(client, body) except Exception as e: print(f"skip {mdx}: {e}") continue if score["deletion_candidate"]: candidates.append({ "path": str(mdx.relative_to(site_dir)), "score": score["score"], "improvements": score["improvements"], }) output.write_text(json.dumps(candidates, ensure_ascii=False, indent=2)) print(f"{len(candidates)} candidates -> {output}")if __name__ == "__main__": for name, dir in [ ("claudelab", Path("/tmp/repos/claudelab.net/content/articles/ja")), ("gemilab", Path("/tmp/repos/gemilab.net/content/articles/ja")), ]: scan_site(dir, Path(f"/tmp/scan_{name}.json"))
A Cowork scheduled task runs this every Monday morning and posts the JSON to Slack. At 100 articles per site across 4 sites the run costs about $0.15. The previous workflow of "manually review one article at a time" used to take hours; this version is recommendation-focused review for 30 minutes.
Four Gotchas Worth Knowing for Solo Operations
Things that bit during the build.
First, GSC API rate limits are per property, not per site.sc-domain: properties that bundle subdomains do not get separate counts. I hit the cap on day one before adding caching. With multiple sites, ttl=3600 is mandatory rather than optional.
Second, Gemini API occasionally violates JSON mode. Even with response_mime_type="application/json", responses prefaced with thinking-out-loud text appear about once per hundred calls. Wrap in try / except json.JSONDecodeError and skip the offender — it is not worth retrying.
Third, @st.cache_data keys arguments by hash, which behaves unexpectedly when you pass a dict or list. Pass each field individually rather than handing in a whole config dict.
Fourth, never commit a service-account JSON key. Use Streamlit's secrets feature, Cloud Run's Secret Manager, or any other secret store — just not the repo. I made this mistake once early on. GitHub's Secret Scanning caught it and I rotated within minutes, but production-grade ops should prevent it upstream.
Results After Seven Months
Quantitatively, the latest 28-day snapshot for the blog sites:
Compared with the manual-review era before the dashboard, deletion-candidate detection precision rose. Lacrima has cleaned out about 80 articles over six months; Mystery about 30. Both saw average position improve by 0.5-1.0 after cleanup.
The qualitative difference is bigger than the numbers. Instead of vibe-judging articles as "kind of meh," the workflow becomes "Gemini scored 58 with a templated-intro callout — let me look." Decisions get rooted in evidence rather than impression, and operational confidence increases without delegating final judgment to the model.
What I Plan to Add Next
Three open items.
First, premium conversion visualization. Wire Stripe webhooks into Streamlit to attribute membership signups to specific articles. GA4 conversion events are close, but Stripe-side exact amounts deserve a direct path.
Second, automated A/B testing. Swap titles and descriptions weekly, have Gemini analyze the resulting CTR shifts. Manual A/B was not realistic at indie scale; automation could be the highest-leverage addition.
Third, cross-site keyword overlap detection. Four Lab sites writing about adjacent topics is at risk of self-cannibalization. A panel that scores query overlap (e.g., "Claude Lab's Claude Code article vs. Gemini Lab's Code Assist article") would catch this before it costs traffic.
Blog operations is not just writing — the cost of looking at numbers stacks invisibly. Consolidating onto one screen lowers the operator's mental load far more than the engineering effort suggests. If you are running multiple sites and burning out on dashboards, the investment pays back fast. I hope these notes help other solo operators in the same situation.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.