Building Semantic Search for a Laravel Markdown Blog with Embeddings
Introduction
Keyword search works well when readers type the exact terms used in your article. Technical blogs are messier than that. A reader may search for "query optimization" while your post is framed around query plans, indexes, or database strategy.
That is where semantic search becomes useful.
For a Markdown-first blog, this is a particularly good fit because the content is clean, structured, and easy to process through an indexing pipeline.
Table of Contents
- Minimal architecture for semantic search
- Why chunking and metadata matter
- Ranking beyond cosine similarity
- A more practical search and indexing flow
- How to evaluate search quality
- Common pitfalls to avoid
A Minimal Architecture
For a personal or small team blog, a good baseline flow looks like this:
- Read all Markdown files.
- Parse frontmatter and body content.
- Chunk the content by sections or headings.
- Generate embeddings for each chunk.
- Store vectors together with useful metadata.
- Embed the user query and rank the closest matches.
namespace App\Services\Search;
class ChunkPostContent
{
public function handle(string $markdown): array
{
$sections = preg_split('/\n##\s+/', $markdown);
return collect($sections)
->map(fn (string $section) => trim($section))
->filter(fn (string $section) => mb_strlen($section) > 120)
->values()
->all();
}
}
Do not overcomplicate the first version. For a few hundred posts, heading-based chunking is often enough.
Metadata Matters
Every stored vector should include metadata such as:
- post slug
- title
- locale
- section heading
- publish date
- tags
Metadata is what makes results explainable, filterable, and easier to debug. A vector without metadata becomes awkward to work with very quickly.
Chunking Is More Important Than Many Tutorials Admit
Most semantic-search tutorials spend a lot of time on vector stores and very little on chunking. In practice, chunking often affects retrieval quality more than switching to a slightly better embedding model.
If chunks are too large:
- each vector carries too many ideas
- search results become broad and generic
- ranking struggles to identify the most relevant section
If chunks are too small:
- important context gets lost
- results become fragmented and hard to read
- vector count grows unnecessarily fast
For a Markdown technical blog, a practical default is:
- split by
##or###headings first - merge extremely short sections into the next chunk
- keep each chunk long enough to stand on its own
Additional Metadata Worth Storing
Alongside the obvious fields, it is often useful to keep:
- a short
excerptfor UI display heading_levelto distinguish major and minor sectionsword_countso you can exclude thin chunksis_draftto ensure unpublished content never leaks into search
Ranking Needs More Than Similarity
Cosine similarity is a starting point, not the whole ranking strategy. For technical content, you usually want additional signals such as:
- a small freshness boost for newer posts
- higher weight when the title matches the intent
- a tag boost for related topics
- exclusion of draft or overly thin content
Good semantic search is guided retrieval, not blind trust in vector scores.
A More Practical Search Flow
Instead of simply taking the top 5 chunks by similarity and showing them directly, a more useful flow is often:
- embed the query
- retrieve the top 20 chunks
- group them by
post_slug - apply business signals such as title match, tag match, and freshness
- keep the top 5 posts
- show the best chunk from each post as the snippet
That prevents a single article from filling the result list with multiple near-duplicate chunks.
Evaluating Search Quality Without Overbuilding
You do not need a full ML evaluation system for a blog. A practical approach is to define 20-30 representative queries such as:
optimize mysql queriessemantic search laravel blogqueue retry ai jobs
Then check:
- does the correct post appear in the top 3?
- is the snippet actually useful?
- are the results too repetitive?
That kind of manual benchmark is cheap and often enough to catch regressions.
A More Realistic Indexing Pipeline
In practice, indexing often needs more than the minimal example:
- load Markdown files
- parse frontmatter
- optionally strip sections that should not dominate search, such as very long code fences
- chunk by heading
- normalize whitespace
- batch embedding calls
- upsert vector records
That pipeline should be idempotent. Reindexing a post should update it cleanly, not create duplicates.
Snippet Generation Also Shapes Perceived Search Quality
Even if retrieval is correct, the search experience still feels weak if the snippet is poor. A few simple rules help a lot:
- prefer the highest-scoring chunk as the snippet source
- trim by sentence or short paragraph, not arbitrary character length
- show the related heading so users know where the result came from
Users often judge search quality not only by ranking, but by whether the snippet makes the result worth clicking.
When to Reindex
Because the blog is Markdown-first, the most practical indexing trigger is when a post is published or updated. A batch command is often enough:
php artisan blog:reindex-search --locale=en
Batch reindexing is easier to control and usually cheaper than reprocessing everything on every deploy.
Common Pitfalls
- indexing draft content accidentally
- using the same chunking strategy for every type of content
- storing too little metadata to debug ranking problems
- trusting similarity scores without testing real user intent
Avoiding those four issues already makes the first version much more useful.
Key takeaways:
- Semantic search is a strong fit for technical blogs because readers use many different phrasings for the same topic.
- Chunking and metadata usually affect retrieval quality more than teams expect.
- Good ranking combines vector similarity with title, tag, and freshness signals.
- A small benchmark query set is often enough to catch search-quality regressions.
- Indexing should be idempotent and must avoid draft leakage or low-value chunks.
Conclusion
Semantic search is one of the most useful AI features you can add to a technical blog. The input is clean, the user benefit is obvious, and the implementation can stay simple if you begin with small chunks, solid metadata, and ranking logic that includes real product signals.