Add basic SEO for the public landing page.

Index the homepage with meta description, Open Graph tags, robots.txt and sitemap; keep private viewer URLs out of search indexes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-05 11:05:45 +02:00
co-authored by Cursor
parent ed906a1b5e
commit 7330d20cca
4 changed files with 59 additions and 3 deletions
+39 -2
View File
@@ -9,7 +9,7 @@ import sys
import webbrowser
from pathlib import Path
from flask import Blueprint, Flask, abort, jsonify, render_template, request, send_file, send_from_directory
from flask import Blueprint, Flask, Response, abort, jsonify, render_template, request, send_file, send_from_directory
from werkzeug.utils import secure_filename
from advisor import advise_skill
@@ -121,6 +121,20 @@ def sw_viewer(viewer_id: str):
return _serve_sw()
def _landing_seo() -> dict[str, str]:
base = external_base_url()
return {
"seo_title": "Idle Fantasy Save Viewer",
"seo_description": (
"Free web viewer for Idle Fantasy game saves. "
"Track skills, inventory, quests, goals and snapshot history no account, private link only."
),
"seo_url": f"{base}/",
"seo_image": f"{base}/static/icon-512.png",
"seo_robots": "index, follow",
}
@viewer_bp.route("/")
def viewer_index(viewer_id: str):
_resolve_viewer_db(viewer_id)
@@ -128,6 +142,7 @@ def viewer_index(viewer_id: str):
"index.html",
viewer_id=viewer_id,
manifest_href=f"/v/{viewer_id}/manifest.webmanifest",
seo_robots="noindex, nofollow",
)
@@ -401,9 +416,31 @@ def sw_root():
return _serve_sw()
@app.route("/robots.txt")
def robots_txt():
base = external_base_url()
body = f"User-agent: *\nAllow: /\nDisallow: /v/\n\nSitemap: {base}/sitemap.xml\n"
return Response(body, mimetype="text/plain")
@app.route("/sitemap.xml")
def sitemap_xml():
base = external_base_url()
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>{base}/</loc>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
"""
return Response(xml, mimetype="application/xml")
@app.route("/")
def landing():
return render_template("landing.html", manifest_href="/manifest.webmanifest")
return render_template("landing.html", manifest_href="/manifest.webmanifest", **_landing_seo())
@app.post("/api/viewers")
+17
View File
@@ -0,0 +1,17 @@
{% if seo_robots %}<meta name="robots" content="{{ seo_robots }}">{% endif %}
{% if seo_description %}
<meta name="description" content="{{ seo_description }}">
<meta property="og:description" content="{{ seo_description }}">
<meta name="twitter:description" content="{{ seo_description }}">
{% endif %}
{% if seo_url %}
<link rel="canonical" href="{{ seo_url }}">
<meta property="og:url" content="{{ seo_url }}">
{% endif %}
{% if seo_title %}
<meta property="og:title" content="{{ seo_title }}">
<meta name="twitter:title" content="{{ seo_title }}">
{% endif %}
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary">
{% if seo_image %}<meta property="og:image" content="{{ seo_image }}">{% endif %}
+1
View File
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Idle Fantasy Viewer</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
{% include '_seo_head.html' %}
{% include '_pwa_head.html' %}
<link rel="stylesheet" href="/static/style.css">
{% include '_analytics.html' %}
+2 -1
View File
@@ -3,8 +3,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Idle Fantasy Viewer</title>
<title>{{ seo_title | default("Idle Fantasy Save Viewer", true) }} Skills, Inventory &amp; History</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
{% include '_seo_head.html' %}
{% include '_pwa_head.html' %}
<link rel="stylesheet" href="/static/style.css">
{% include '_analytics.html' %}