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.

How-to diagram showing Wix Studio and Velo advanced SEO capabilities including dynamic meta tags, custom schema markup, CMS database pages, multilingual hreflang, and A/B testing
Wix Studio and Velo unlock advanced SEO capabilities that go far beyond what the standard Wix editor provides.

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 }
    ];
  }
});
A/B Testing Tip: Only test one SEO element at a time to isolate its impact. If you change both the title and meta description simultaneously, you will not know which change drove the result. Start with title tag testing because it has the most direct impact on both rankings and CTR, then test meta descriptions in a separate experiment.

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
  });
}
Server-Side SEO Power: HTTP functions and routers are the most underused SEO tools in the Velo ecosystem. Most Wix developers never touch them, which means most Wix sites miss out on custom sitemaps, intelligent redirects, and server-side logic that can give you a significant competitive edge. If you are comfortable with JavaScript, these backend tools transform Wix from a simple website builder into a fully programmable SEO platform.


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

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

HTTP Function Testing: Test HTTP functions by visiting the endpoint URL directly in your browser. For XML responses like sitemaps, use a browser extension or copy the URL into an XML validator to verify the output is well-formed. For router functions, test with both valid and invalid slugs to verify the redirect, content serving, and 404 handling logic all work correctly.

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.