Publish in rankveo, render the blog on your own Django site. The best SEO, GEO and AEO content tooling, on your domain — server rendered HTML, not an embed.
pip install rankveo-django# settings.py
INSTALLED_APPS = [..., "django.contrib.sitemaps", "rankveo"]
RANKVEO = {"API_KEY": os.environ["RANKVEO_BLOG_API_KEY"]}# urls.py
path("blog/", include("rankveo.urls")),That is the whole setup.
| Route | |
|---|---|
/blog/ |
Paginated listing |
/blog/page/2/ |
Real paths, not ?page= — every page is crawlable |
/blog/<slug>/ |
Article, with Open Graph and BlogPosting JSON-LD |
/blog/tag/<slug>/, /blog/category/<slug>/ |
Filtered listings, paginated |
/blog/feed/ |
RSS, via django.contrib.syndication |
/blog/sitemap.xml |
django.contrib.sitemaps plus image entries — Google will not index an image it has to find by crawling |
RANKVEO = {..., "REVALIDATE_SECRET": os.environ["RANKVEO_REVALIDATE_SECRET"]}rankveo then calls POST /blog/rankveo/revalidate whenever an article is
published, edited while live, unpublished or deleted, and the blog cache is
cleared. Without a secret the route is not registered at all — an
unauthenticated cache-busting endpoint is a free way to make every request hit
the API.
This is where Django beats a static site generator: there is a real cache to invalidate, so nothing has to rebuild or redeploy.
from rankveo.cache import blog
articles = blog().get_articles(page=0)
article = blog().find_article("how-to-rank-in-ai-answers")
site = blog().get_site()Responses go through Django's cache framework. blog().client is the uncached
client; blog().flush() clears everything.
Override any template by putting your own at the same path — the app template loader finds yours first:
templates/rankveo/blog/base.html # put the blog inside your site chrome
templates/rankveo/blog/index.html
templates/rankveo/blog/article.html
base.html is deliberately plain. Point it at your own layout and the rest
inherits. The class names are the same semantic ones every rankveo starter uses.
| Key | Default |
|---|---|
API_KEY |
— (required) |
BASE_URL |
https://api.rankveo.com/api |
TIMEOUT |
10.0 |
CACHE_TIMEOUT |
3600 — 0 disables caching |
CACHE_ALIAS |
"default" |
PER_PAGE |
9 |
SITE_URL |
derived from the request |
SITE_NAME, SITE_DESCRIPTION |
"Blog", "" |
REVALIDATE_SECRET |
None |
- The key is server-side. It is never needed in a browser.
- Body images are already in
article.html. Looping overarticle.imagesas well prints them twice — usearticle.gallery_imagesfor the rest. - An article from a listing has no body.
article.has_bodysays so; it is the usual cause of a blank article page. - The cache stores plain dicts, not dataclasses. Django pickles cache values, and a pickled dataclass breaks in production the first time a field is renamed. Objects are rebuilt on read instead.
MIT.