{"slug": "stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini", "title": "Stećak Oracle: Exploring Medieval Stone Monuments with Sanity and Gemini", "summary": "A developer built Stećak Oracle, a knowledge-driven web application that combines Sanity, Next.js, and Google Gemini to answer questions about stećci, the medieval tombstone monuments found across Bosnia and Herzegovina and neighboring countries. The project stores structured historical records — monuments, motifs, and sources drawn from the UNESCO World Heritage Centre — in Sanity as a knowledge layer, with Gemini serving only as the generation layer so that AI output is grounded in documented evidence rather than treated as the source of truth. The developer initially experimented with the Anthropic API but switched to Google Gemini after lacking API credits.", "body_md": "I decided to build something around a subject that is very close to where I live: **stećci**, the medieval tombstone monuments found across Bosnia and Herzegovina and neighboring countries.\n\nThe result is **Stećak Oracle**, a small knowledge-driven web application that combines **Sanity**, **Next.js**, and **Google Gemini**.\n\nThe idea is simple:\n\nStore structured historical information in Sanity, then let an AI model answer questions using that information as its knowledge base.\n\nThis project was built for the **Sanity Challenge**.\n\nStećci are medieval tombstones found across Bosnia and Herzegovina, Croatia, Montenegro and Serbia.\n\nThere is a huge amount of historical information around them, but I wanted to approach the subject from a developer's perspective.\n\nInstead of building another static website with paragraphs of text, I wanted to experiment with a small structured knowledge system.\n\nThe application would contain:\n\nThen I wanted to put an AI interface on top of that data.\n\nThat became Stećak Oracle.\n\nThe first important decision was how to structure the information.\n\nI didn't want everything to be stored as one large JSON document or hardcoded directly into the application.\n\nSanity was a good fit because I could create actual content types and relationships between them.\n\nThe main content model became:\n\n```\nStećak\n ├── Motifs\n ├── Sources\n └── Historical information\n\nMotif\n ├── Description\n ├── Interpretations\n └── Interpretation caution\n\nSource\n ├── Title\n ├── URL\n ├── Publisher\n ├── Type\n └── Notes\n```\n\nThis also made the project more interesting from a development perspective.\n\nThe AI doesn't have to be the source of truth.\n\nSanity is the structured knowledge layer.\n\nGemini is the generation layer.\n\nI created three main document types in Sanity.\n\nA stećak contains information such as:\n\nFor example, one of the records is:\n\n**Stećak Radimlja — Vojvoda Vlatko Vuković**\n\nwith Radimlja near Stolac as the location.\n\nMotifs are separate documents because the same motif can appear on multiple monuments.\n\nFor example, the project contains a `Sword` motif.\n\nBut there is an important detail here.\n\nI didn't want to tell the AI that a particular interpretation is an absolute historical fact.\n\nSo the motif also contains an **Interpretation Caution** field.\n\nThat allows the application to distinguish between documented information and interpretation.\n\nSources are separate documents as well.\n\nFor the initial dataset I used institutional information from the **UNESCO World Heritage Centre**, including the Stećci Medieval Tombstone Graveyards entry.\n\nThis gives the application a simple evidence layer rather than treating AI-generated text as the source itself.\n\nThe frontend is built with **Next.js** and TypeScript.\n\nThe application has several main areas:\n\n```\n/\n├── Home\n├── /stecci\n├── /stecci/[slug]\n├── /motifs\n├── /sources\n└── /oracle\n```\n\nThe homepage retrieves the current number of records directly from Sanity.\n\nThat means the statistics are not hardcoded.\n\nIf I add another stećak or source in Sanity, the application can reflect that change.\n\nEach stećak has its own dynamic page.\n\nFor example:\n\n```\n/stecci/stecak-radimlja-vojvoda-vlatko-vukovic\n```\n\nThe page retrieves the document from Sanity using its slug.\n\nIt also dereferences related motifs and sources.\n\nThis was useful because the frontend doesn't need to duplicate the actual content model.\n\nSanity remains responsible for the data.\n\nThe final piece was the AI interface.\n\nThe Oracle page allows a user to ask a question about:\n\nWhat does the sword motif on the Radimlja stećak represent?\n\nThe frontend sends the question to a Next.js API route.\n\nThe basic flow is:\n\n```\nUser\n  ↓\nOracle UI\n  ↓\nNext.js API route\n  ↓\nSanity\n  ↓\nKnowledge base\n  ↓\nGemini\n  ↓\nAnswer\n```\n\nI initially experimented with the Anthropic API.\n\nThe API itself worked, but I didn't have API credits available, so I decided not to add another paid dependency to the project.\n\nI switched to Google Gemini through the `@google/genai` package.\n\nThe application uses an environment variable for the API key:\n\n```\nGEMINI_API_KEY=...\n```\n\nThe key is stored in `.env.local` and is excluded from Git.\n\nThe actual API request is made server-side through the Next.js API route.\n\nThis is important because the API key should not be exposed in browser-side JavaScript.\n\nThis is probably the most important technical part of the project.\n\nI didn't want to simply send the user's question to Gemini and display whatever it returned.\n\nInstead, the API route first retrieves the knowledge base from Sanity.\n\nConceptually:\n\n``` js\nconst knowledgeBase = await getKnowledgeBase()\n```\n\nThe query retrieves:\n\n```\nStećci\nMotifs\nSources\n```\n\nincluding their relationships.\n\nThe resulting data is then serialized and included in the Gemini prompt.\n\nThe model receives something conceptually like:\n\n```\nHere is the knowledge base from Sanity:\n\n{\n  \"stecci\": [...],\n  \"motifs\": [...],\n  \"sources\": [...]\n}\n\nYou are the Stećak Oracle...\n\nAnswer this question clearly and carefully:\n\n[user question]\n```\n\nI also explicitly tell the model:\n\n```\n- Do not invent historical facts.\n- Distinguish documented facts from interpretation.\n- If the available information is insufficient, say so.\n- Keep the answer concise.\n```\n\nFor a small dataset like this, sending the complete knowledge base is simple enough.\n\nFor a much larger project, I would definitely replace this with retrieval of only the most relevant documents.\n\nI tested the Oracle with:\n\nThe response used the information stored in Sanity.\n\nIt identified the sword as a recurring figural motif and, importantly, preserved the caution around interpretation.\n\nThe answer did not present the interpretation as an absolute fact.\n\nThat distinction was intentional.\n\nHistorical subjects are a good example of why an AI application should be careful about the difference between:\n\n```\ndocumented information\n```\n\nand\n\n```\ninterpretation\n```\n\nOne thing I wanted to avoid was building a generic chatbot and simply giving it a historical theme.\n\nThe interesting part of the project is the relationship between the CMS and the AI model.\n\nThe architecture is closer to:\n\n```\n             ┌───────────────┐\n             │     User      │\n             └───────┬───────┘\n                     │\n                     ▼\n             ┌───────────────┐\n             │    Next.js    │\n             │    Frontend   │\n             └───────┬───────┘\n                     │\n                     ▼\n             ┌───────────────┐\n             │  API Route    │\n             └───────┬───────┘\n                     │\n             ┌───────┴────────┐\n             ▼                ▼\n      ┌────────────┐   ┌────────────┐\n      │   Sanity   │   │   Gemini   │\n      │ Knowledge  │──▶│    AI      │\n      │   Base     │   │            │\n      └────────────┘   └──────┬─────┘\n                              │\n                              ▼\n                       ┌────────────┐\n                       │   Answer   │\n                       └────────────┘\n```\n\nSanity provides the structured information.\n\nGemini turns that information into a natural-language answer.\n\nNext.js connects everything together.\n\nThe project ended up teaching me a few things that were more interesting than I expected.\n\nIt is tempting to start an AI project with only a prompt.\n\nBut once you have structured content, relationships and sources, you can build something much more controlled.\n\nThe content model became just as important as the AI integration.\n\nFor historical information, I don't want the model to confidently invent an interpretation.\n\nThat's why the knowledge base includes fields such as:\n\n```\nInterpretations\nInterpretation Caution\nSources\n```\n\nThe application can then give the model some context about how information should be treated.\n\nI didn't need hundreds of monuments to build the first version.\n\nTwo stećak records, a motif and documented sources were enough to demonstrate the architecture.\n\nThe important part was making the entire pipeline work.\n\nFor this challenge, I intentionally didn't build a complicated vector database or RAG pipeline.\n\nThe dataset is small enough that retrieving the complete knowledge base is reasonable.\n\nIf the project grows, the architecture can evolve.\n\nThere are several things I would add to a future version.\n\nInstead of sending every Sanity document to Gemini, I would retrieve only the documents relevant to the question.\n\n```\nQuestion\n   ↓\nSearch Sanity\n   ↓\nRelevant stećak\nRelevant motifs\nRelevant sources\n   ↓\nGemini\n```\n\nThat would scale much better.\n\nThe obvious next step is a larger dataset covering more locations across Bosnia and Herzegovina and the wider region.\n\nThe next version could show sources directly underneath individual claims or sections of an answer.\n\nThat would make the evidence trail more visible.\n\nThe current version is deliberately data-first.\n\nI didn't have a suitable image collection for every monument, so I chose not to invent one or fill the application with unrelated images.\n\nA future version could add properly sourced photographs and image metadata to the Sanity model.\n\nI would also add:\n\nThat would make the knowledge base useful even without the AI interface.\n\ngithub repo:[https://github.com/dkljajo/Ste-ak-Oracle-frontend-and-Gemini-integration](https://github.com/dkljajo/Ste-ak-Oracle-frontend-and-Gemini-integration)\n\nThe current version uses:\n\nThe application is intentionally small.\n\nThere is no separate backend service.\n\nThe Next.js API route handles the server-side Gemini request and retrieves the Sanity data.\n\nThe important parts of the repository look roughly like this:\n\n```\nstecak-oracle/\n├── studio/\n│   ├── schemaTypes/\n│   │   ├── stecak.ts\n│   │   ├── motif.ts\n│   │   └── source.ts\n│   └── sanity.config.ts\n│\n└── web/\n    ├── app/\n    │   ├── api/\n    │   │   └── oracle/\n    │   │       └── route.ts\n    │   ├── motifs/\n    │   ├── oracle/\n    │   ├── sources/\n    │   └── stecci/\n    └── lib/\n        └── sanity.ts\n```\n\nThis separation also makes the architecture easy to understand:\n\n```\nstudio/\n    → content model and Sanity configuration\n\nweb/\n    → application and AI interface\n```\n\nStećak Oracle started as a simple idea:\n\nWhat if I could combine a structured historical knowledge base with an AI interface?\n\nThe final application is still small, but the complete pipeline works:\n\n```\nSanity\n  ↓\nStructured historical knowledge\n  ↓\nNext.js\n  ↓\nGemini\n  ↓\nGrounded answer\n```\n\nAnd that's what I wanted to demonstrate with this project.\n\nNot a generic chatbot.\n\nNot an AI that simply talks about stećci.\n\nA small application where the content has structure, sources and relationships, and where the AI uses that content to generate an answer.\n\nThere is a lot of room to take this further.\n\nI would like to expand the dataset, improve retrieval, add properly sourced images, expose citations more clearly, and eventually turn the Oracle into a more complete exploration tool for stećci.\n\nFor now, I'm happy that the basic idea works end-to-end.\n\nThe source code is available on GitHub:\n\n**Stećak Oracle — Sanity + Next.js + Gemini**\n\nThis project was built as my submission for the **Sanity Challenge**.\n\nThe challenge gave me a good excuse to combine several things I've been learning and working with: cloud technologies, application development, structured content, APIs and AI.\n\nMore importantly, it gave me a reason to build something connected to the history and culture of the region I live in.\n\nThat was the part I enjoyed most.\n\nStećak Oracle is a small project, but it brought together several technologies that I wanted to understand better.\n\nSanity handles the content.\n\nNext.js handles the application.\n\nGemini handles the natural-language generation.\n\nAnd the interesting part is what happens between them.\n\nThe result is a simple example of how a structured CMS can become the foundation for a grounded AI application.\n\nThanks for reading.\n\nIf you have ideas for improving the project, I'd be interested in hearing them.\n\n`#sanity` `#nextjs` `#typescript` `#ai` `#gemini` `#webdev`", "url": "https://wpnews.pro/news/stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini", "canonical_source": "https://dev.to/davidkljajo/-stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini-4hk9", "published_at": "2026-09-19 16:22:13+00:00", "updated_at": "2026-09-19 16:53:42.491604+00:00", "lang": "en", "topics": ["ai-products", "ai-tools", "generative-ai", "developer-tools"], "entities": ["Sanity", "Next.js", "Google Gemini", "Anthropic", "UNESCO World Heritage Centre", "Stećak Oracle", "Radimlja", "Vojvoda Vlatko Vuković"], "alternates": {"html": "https://wpnews.pro/news/stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini", "markdown": "https://wpnews.pro/news/stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini.md", "text": "https://wpnews.pro/news/stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini.txt", "jsonld": "https://wpnews.pro/news/stecak-oracle-exploring-medieval-stone-monuments-with-sanity-and-gemini.jsonld"}}