The Invoice Page That Took 4 Seconds to Search
How preloading product data cut search latency from 4 seconds to instant on a POS invoice page, and when this pattern breaks down.
In Polaris, the POS system I built for retail shops, the invoice page has a product search bar. A cashier types a product name, results appear, and they click to add it to the invoice.
The first version fired an API call on every keystroke. On a shop with 500 products, search took 3-4 seconds, because each request hit the database, serialized the response and traveled back over the network. Cashiers waited on every search, which hurt during rush hours.
Loading the catalog on mount
The product catalog for a typical Polaris shop is 200-800 products, maybe 50KB of JSON. There's no reason to fetch it on every search when you can load it once.
<script setup>
import { ref, onMounted } from 'vue'
import { useProductStore } from '@/stores/product'
const productStore = useProductStore()
const searchQuery = ref('')
const searchResults = ref([])
onMounted(async () => {
await productStore.fetchProducts()
})
function searchProducts() {
if (!searchQuery.value.trim()) {
searchResults.value = []
return
}
const query = searchQuery.value.toLowerCase()
searchResults.value = productStore.products.filter(p =>
p.name.toLowerCase().includes(query) ||
p.sku?.toLowerCase().includes(query)
)
}
</script>Search went from 3-4 seconds to effectively instant. Fetching the catalog added maybe 200ms to the initial page load, but that happens once while the page is rendering anyway, and the cashier doesn't notice.
When this breaks down
This pattern has a ceiling. It works for Polaris because:
- The catalog size is bounded. Retail shops have hundreds of products. If you're building an e-commerce platform with 50,000 SKUs, preloading everything is not an option.
- The data changes slowly. Products don't update mid-transaction. If your data changes every few seconds, you'll serve stale results.
- The search is simple string matching on name and SKU. If you need fuzzy matching, relevance scoring or faceted search, you need a server-side search engine.
Options for bigger catalogs
For larger catalogs, the progression is:
- Debounced server search. Wait 300ms after the last keystroke, then fire one API call, which stops the per-keystroke storm.
- Server-side search with an index, such as PostgreSQL trigram indexes or Elasticsearch. You need this once you're past a few thousand items.
- Virtual scrolling for results. If you're showing 10,000 results in a dropdown, render only what's visible; libraries like
vue-virtual-scrollerhandle this.
Which one you need depends on how much data you have and how fast it changes. For Polaris, preloading 500 products on mount was the simplest fix for a cashier waiting four seconds per search.