Custom HTTP functions and advanced server-side SEO with Velo
Module 20: Wix Studio & Velo Advanced SEO | Lesson 253 of 687 | 30 min read
By Michael Andrews, Wix SEO Expert UK
Most Wix SEO work happens in the frontend: setting meta tags, generating schema, optimizing content. But some of the most powerful SEO techniques require server-side logic that runs before the page is ever delivered to the browser or the bot. Velo HTTP functions let you create custom API endpoints on your Wix site, and Velo routers let you intercept URL requests with server-side logic. Together, these tools enable custom sitemaps, programmatic redirect systems, SEO A/B testing, and intelligent 404 handling that would otherwise require a standalone server.

Understanding Velo HTTP Functions for SEO
Velo HTTP functions are server-side endpoints you create in the backend/http-functions.js file. Each function corresponds to a URL pattern on your site: a function named get_customSitemap becomes accessible at yoursite.com/_functions/customSitemap via GET request. These functions execute on Wix servers, have access to your CMS data, and can return any HTTP response including XML, JSON, or plain text.
For SEO, the key advantage is that HTTP functions run entirely server-side with no client JavaScript rendering required. Search engine bots receive the response directly, exactly as your function constructs it. This makes HTTP functions ideal for generating XML sitemaps, serving robots.txt modifications, creating JSON API endpoints for external SEO tools, and building webhook receivers for content syndication platforms.
import { ok, notFound, serverError } from 'wix-http-functions';
import wixData from 'wix-data';
export async function get_customSitemap(request) {
try {
const result = await wixData.query('Products')
.eq('isPublished', true)
.ne('noIndex', true)
.limit(1000)
.find();
const baseUrl = 'https://www.yourstore.com';
let xml = '<?xml version="1.0" encoding="UTF-8"?>';
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
result.items.forEach(item => {
xml += '<url>';
xml += '<loc>' + baseUrl + '/products/' + item.slug + '</loc>';
xml += '<lastmod>' + new Date(item._updatedDate).toISOString().split('T')[0] + '</lastmod>';
xml += '<changefreq>weekly</changefreq>';
xml += '<priority>0.8</priority>';
xml += '</url>';
});
xml += '</urlset>';
return ok({
headers: { 'Content-Type': 'application/xml' },
body: xml
});
} catch (error) {
return serverError({ body: 'Sitemap generation failed' });
}
}
Building a Custom Sitemap System
The default Wix sitemap works well for most sites, but it has limitations. It includes all published pages regardless of their SEO value, does not support custom priority or changefreq values per URL, and cannot include URLs from external systems or custom routing logic. A custom sitemap built with HTTP functions gives you complete control over which URLs are included, their metadata, and how the sitemap is structured.
For large sites with more than 50,000 URLs, you need a sitemap index that references multiple individual sitemaps. The sitemap protocol limits each sitemap file to 50,000 URLs and 50MB uncompressed. Your HTTP function can generate a sitemap index that points to paginated sitemap files, each of which is also served by an HTTP function with offset and limit parameters.
export async function get_sitemapIndex(request) {
const collections = [
{ name: 'Products', count: 5000 },
{ name: 'BlogPosts', count: 800 },
{ name: 'Locations', count: 200 }
];
const baseUrl = 'https://www.yourstore.com';
let xml = '<?xml version="1.0" encoding="UTF-8"?>';
xml += '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
collections.forEach(col => {
const pages = Math.ceil(col.count / 1000);
for (let i = 0; i < pages; i++) {
xml += '<sitemap>';
xml += '<loc>' + baseUrl + '/_functions/sitemap_' + col.name.toLowerCase() + '?page=' + i + '</loc>';
xml += '<lastmod>' + new Date().toISOString().split('T')[0] + '</lastmod>';
xml += '</sitemap>';
}
});
xml += '</sitemapindex>';
return ok({
headers: { 'Content-Type': 'application/xml' },
body: xml
});
}
Dynamic Redirect Systems with HTTP Functions
URL redirects are essential during site migrations, product discontinuations, and URL restructuring. While Wix offers a built-in redirect manager, it has a limit on the number of redirects and does not support pattern-based or conditional redirects. A Velo-powered redirect system stores redirect rules in a CMS collection and evaluates them server-side, supporting thousands of redirects with pattern matching and conditional logic.
Store your redirect rules in a collection with fields for source URL pattern, destination URL, redirect type (301 permanent or 302 temporary), and an active/inactive flag. Your HTTP function or router queries this collection on each request and returns the appropriate redirect response. This approach scales to tens of thousands of redirects and can be updated by non-technical team members through the CMS interface.
import { ok, redirect, notFound } from 'wix-router';
import wixData from 'wix-data';
export async function products_Router(request) {
const slug = request.path[0];
const redirectResult = await wixData.query('Redirects')
.eq('sourceSlug', slug)
.eq('isActive', true)
.find();
if (redirectResult.items.length > 0) {
const rule = redirectResult.items[0];
return redirect(rule.destinationUrl, rule.redirectType === '301' ? '301' : '302');
}
const productResult = await wixData.query('Products')
.eq('slug', slug)
.eq('isPublished', true)
.find();
if (productResult.items.length === 0) {
return notFound();
}
return ok('product-page', productResult.items[0]);
}
Server-Side Logic for SEO A/B Testing
SEO A/B testing allows you to test different title tags, meta descriptions, or page content variations to determine which version produces better rankings or click-through rates. Unlike traditional A/B testing where you split traffic between two page versions, SEO A/B testing assigns a variation to a page permanently (for the duration of the test) so that Googlebot consistently sees the same version and can evaluate it fairly.
Implement this by creating a CMS collection that maps page slugs to test variations. The router or page-level Velo code queries this collection and applies the assigned variation. Each variation includes its own title, meta description, and any other elements being tested. After sufficient time (typically 4-8 weeks), compare rankings and CTR data in Google Search Console to determine the winner.
import wixData from 'wix-data';
import wixSeo from 'wix-seo';
$w.onReady(async function () {
const routerData = $w('#dynamicDataset').getCurrentItem();
const slug = routerData.slug;
const testResult = await wixData.query('SeoTests')
.eq('pageSlug', slug)
.eq('isActive', true)
.find();
if (testResult.items.length > 0) {
const test = testResult.items[0];
wixSeo.title = test.testTitle;
wixSeo.metaTags = [
{ name: 'description', content: test.testDescription }
];
} else {
wixSeo.title = routerData.seoTitle || routerData.name + ' | YourBrand';
wixSeo.metaTags = [
{ name: 'description', content: routerData.metaDescription }
];
}
});
Custom 404 Handling with Velo Routers
A well-designed 404 system does more than show an error page. It logs missing URLs to help you identify broken links and redirect opportunities, suggests relevant content to keep users on your site, and returns the correct HTTP status code so search engines know the page is genuinely gone rather than temporarily unavailable. Velo routers give you full control over this behavior.
When a router cannot find a matching item for the requested slug, instead of returning a generic 404, query your CMS for similar items and pass them to a custom 404 page template. Log the requested URL to a "BrokenUrls" collection for later review. If the URL matches a known pattern from a previous site structure, automatically redirect to the new equivalent.
import { ok, notFound, redirect } from 'wix-router';
import wixData from 'wix-data';
export async function blog_Router(request) {
const slug = request.path[0];
const postResult = await wixData.query('BlogPosts')
.eq('slug', slug)
.eq('isPublished', true)
.find();
if (postResult.items.length > 0) {
return ok('blog-post-page', postResult.items[0]);
}
const redirectResult = await wixData.query('Redirects')
.eq('sourceSlug', slug)
.find();
if (redirectResult.items.length > 0) {
return redirect(redirectResult.items[0].destinationUrl, '301');
}
await wixData.insert('BrokenUrls', {
requestedUrl: '/blog/' + slug,
referrer: request.referrer || 'direct',
timestamp: new Date(),
resolved: false
});
const relatedPosts = await wixData.query('BlogPosts')
.eq('isPublished', true)
.descending('publishDate')
.limit(5)
.find();
return notFound('custom-404-page', {
requestedSlug: slug,
suggestedPosts: relatedPosts.items
});
}
Rate Limiting and Caching for Server-Side SEO Functions
HTTP functions that generate sitemaps or handle redirects can receive significant traffic, both from search engine bots and from malicious scrapers. Without caching, every request triggers a fresh database query, which can exhaust your Wix data quotas and slow down response times. Implement simple in-memory caching by storing the generated response in a module-level variable with a timestamp, and serving the cached version until it expires.
For sitemaps specifically, a cache duration of 1-6 hours is appropriate. Your product catalog does not change every minute, and search engines typically re-fetch sitemaps on a daily or weekly basis. For redirect lookups, consider loading all active redirects into memory on the first request and refreshing the cache every 30 minutes, rather than querying the database on every single page request.
import { ok } from 'wix-http-functions';
import wixData from 'wix-data';
let cachedSitemap = null;
let cacheTimestamp = 0;
const CACHE_DURATION = 3600000;
export async function get_productSitemap(request) {
const now = Date.now();
if (cachedSitemap && (now - cacheTimestamp) < CACHE_DURATION) {
return ok({
headers: { 'Content-Type': 'application/xml' },
body: cachedSitemap
});
}
const result = await wixData.query('Products')
.eq('isPublished', true)
.limit(1000)
.find();
const baseUrl = 'https://www.yourstore.com';
let xml = '<?xml version="1.0" encoding="UTF-8"?>';
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
result.items.forEach(item => {
xml += '<url>';
xml += '<loc>' + baseUrl + '/products/' + item.slug + '</loc>';
xml += '<lastmod>' + new Date(item._updatedDate).toISOString().split('T')[0] + '</lastmod>';
xml += '</url>';
});
xml += '</urlset>';
cachedSitemap = xml;
cacheTimestamp = now;
return ok({
headers: { 'Content-Type': 'application/xml' },
body: xml
});
}
Complete How-To Guide: Building Custom HTTP Functions and Routers for SEO
This guide walks you through creating custom sitemaps, programmatic redirect systems, and intelligent 404 handling using Velo HTTP functions and routers on your Wix site.
How to implement server-side SEO tools with Velo
- Step 1: Enable Velo on your Wix site. In the code editor, navigate to the backend folder. Create a new file called http-functions.js if it does not already exist. This is where all your HTTP function endpoints will live.
- Step 2: Build your first custom sitemap function. Import ok, notFound, and serverError from wix-http-functions and wixData from wix-data. Create an exported async function named get_customSitemap that queries your Products collection filtered by isPublished equals true.
- Step 3: In the sitemap function, build an XML string following the sitemaps.org protocol. Start with the XML declaration and urlset element. Loop through query results, adding a url element for each item with loc, lastmod (from the item _updatedDate), changefreq, and priority values.
- Step 4: Return the XML using ok() with Content-Type set to application/xml. Wrap the entire function in a try-catch block returning serverError on failure. Test the endpoint by visiting yoursite.com/_functions/customSitemap.
- Step 5: For sites with more than 1000 URLs, build a sitemap index function. Create get_sitemapIndex that lists individual sitemap files for each collection. Each individual sitemap function accepts a page query parameter for pagination with 1000 items per page.
- Step 6: Add caching to your sitemap function. Declare a module-level variable for the cached XML and a timestamp. On each request, check if the cache is still valid (less than 1 hour old). Serve the cached version if valid, otherwise regenerate and update the cache.
- Step 7: Build a CMS-driven redirect system. Create a Redirects collection in your CMS with fields for sourceSlug, destinationUrl, redirectType (301 or 302), and isActive boolean. Create a router file (routers.js) that queries this collection on each request.
- Step 8: In your router function, first check the Redirects collection for a matching source slug. If found, return redirect() with the destination URL and the specified redirect type. If not found, proceed to query the main content collection for the requested item.
- Step 9: Implement intelligent 404 handling. When no matching item is found and no redirect exists, log the broken URL to a BrokenUrls collection with the requested path, referrer, timestamp, and a resolved boolean set to false.
- Step 10: Before returning the 404 response, query your main collection for the 5 most recent published items. Pass these as suggested content to a custom 404 page template using notFound('custom-404-page', { suggestedPosts: relatedItems }).
- Step 11: Submit your custom sitemap URL to Google Search Console. Navigate to Sitemaps in GSC and add yoursite.com/_functions/customSitemap. Monitor the submission status and fix any reported errors.
- Step 12: Set up a monthly routine to review the BrokenUrls collection. Sort by frequency of hits and create redirects for the most-requested broken URLs. Check if any represent content gaps that you should fill by creating new pages at those URLs.
How to Build Custom HTTP Functions and Routers for SEO on Wix
Velo HTTP functions let you create server-side SEO endpoints including custom sitemaps, redirect logic, and intelligent 404 handling. These steps walk you through building each component.
How to create custom sitemaps and routers using Velo HTTP functions
- Step 1: Log in to your Wix Dashboard and open the Wix Editor. Enable Dev Mode by clicking Dev Mode in the editor header. In the sidebar, navigate to Backend Files and create a new file called http-functions.js.
- Step 2: At the top of http-functions.js, add the imports: import { ok, notFound, serverError } from 'wix-http-functions'; and import wixData from 'wix-data';
- Step 3: Create your first HTTP function by adding: export async function get_customSitemap(request) { }. The prefix get_ tells Wix to respond to GET requests at the URL yoursite.com/_functions/customSitemap.
- Step 4: Inside the function, query your primary CMS collection: const result = await wixData.query('Products').eq('isPublished', true).limit(1000).find(); This retrieves all published products for the sitemap.
- Step 5: Build the XML string. Start with: let xml = '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'; Then loop through result.items to add each URL with loc, lastmod, changefreq, and priority elements.
- Step 6: Return the response using: return ok({ headers: { 'Content-Type': 'application/xml' }, body: xml + '</urlset>' }); Wrap the entire function in try-catch and return serverError() if an exception occurs.
- Step 7: Add in-memory caching to your sitemap function. Declare module-level variables: let cachedSitemap = null; and let cacheTimestamp = 0; Check at the start of the function whether the cache is still valid (less than one hour old) and serve it directly.
- Step 8: In the Backend Files section, create a file called routers.js. Import required functions: import { ok, notFound, redirect } from 'wix-router'; and import wixData from 'wix-data';
- Step 9: Create a router function that checks your Redirects CMS collection first: query the collection for a matching sourceSlug. If found, return redirect(item.destinationUrl, '301'). If not, query your main content collection for the item.
- Step 10: Add 404 logging to your router. When no item and no redirect is found, insert a record into a BrokenUrls CMS collection with requestedUrl, referrer (from request.referrer), and timestamp. Then return notFound() with related content suggestions.
- Step 11: Test your HTTP function by visiting yoursite.com/_functions/customSitemap in a browser. For the router, test with both a valid slug and an intentionally invalid slug. Verify the XML output and 404 logging work correctly.
- Step 12: Submit the custom sitemap URL (yoursite.com/_functions/customSitemap) to Google Search Console. Navigate to Sitemaps in the left menu, enter the URL, and click Submit. Monitor the submission status for errors.
This lesson on Custom HTTP functions and advanced server-side SEO with Velo is part of Module 20: Wix Studio & Velo Advanced SEO in The Most Comprehensive Complete Wix SEO Course in the World (2026 Edition). Created by Michael Andrews, the UK's No.1 Wix SEO Expert with 14 years of hands-on experience, 750+ completed Wix SEO projects and 425+ verified five-star reviews.