关注

Food Truck WordPress Website Setup: Location, SEO & Mobile That Works

The $800 Website Decision That Changed How Our Food Truck Gets Found

My wife and I have been running a food truck since 2019. We started with a secondhand truck, a borrowed commercial kitchen license, and a Instagram account that a friend helped us set up. For the first two years, Instagram was our entire digital presence. We posted our location every morning, responded to DMs about where we'd be that weekend, and watched our follower count slowly climb.

It worked, kind of. But we had a problem that every food truck operator hits at some point: Instagram's algorithm stopped showing our location posts to the people who'd followed us specifically to know where we were. Reach dropped. Regulars started messaging us saying they'd missed us at spots they would have come to. We were generating demand we couldn't reliably convert because the information wasn't reaching the people who wanted it.

The website came out of that frustration. Not a website to look professional — we weren't trying to impress investors. A website to solve a specific operational problem: making sure that someone who wants to find us can find us, on any given day, without having to know which social platform to check or hope the algorithm shows them our post.

This is the full account of how we built it, what actually cost us money and time, and what we'd do differently.


The Food Truck Website Problem Nobody Talks About

Most food truck website advice focuses on aesthetics — make it look good, show your menu, put some photos of happy customers. All of that matters, but it misses the primary thing that makes a food truck website different from any other food business website.

A food truck's inventory is time and location, not just food.

A brick-and-mortar restaurant's address is a permanent asset. Their "Find Us" page doesn't change. A food truck's location changes daily, sometimes multiple times a day if they do lunch and dinner at different spots. The website's most critical content — where are you right now, where will you be tomorrow — is also the content that goes stale fastest.

Most food truck websites I looked at before building ours had one of two solutions to this problem:

The bad solution: A static "Schedule" page that the owner manually updates whenever they remember. Usually two weeks out of date. Sometimes listing locations that no longer happen because the permit fell through or the venue relationship changed. Actively misleading to the people who check it.

The slightly better but still annoying solution: A link to their Instagram or a Bandsintown-style event aggregator where you have to check a third-party platform. Fine, but it adds friction and doesn't make the website itself useful.

What we actually needed was a schedule system that: updated in near-real-time, showed our next five appearances with enough information to actually find us (not just "Downtown Farmers Market" — the actual address and time), and didn't require us to log into a website every morning to update it.

That's what we built. Here's how.


Building the Location Schedule System

We use a combination of a custom WordPress post type for location events and a Google Calendar sync that pulls our schedule automatically.

First, the custom post type. In our child theme's functions.php:

add_action('init', function() {
    register_post_type('truck_location', [
        'labels'       => [
            'name'          => 'Locations',
            'singular_name' => 'Location',
            'add_new_item'  => 'Add New Stop',
        ],
        'public'       => true,
        'has_archive'  => false,
        'supports'     => ['title', 'editor', 'custom-fields'],
        'menu_icon'    => 'dashicons-location',
        'rewrite'      => ['slug' => 'locations'],
    ]);

    // Meta fields for each location stop
    register_meta('post', 'stop_date',    ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'string']);
    register_meta('post', 'stop_start',   ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'string']);
    register_meta('post', 'stop_end',     ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'string']);
    register_meta('post', 'stop_address', ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'string']);
    register_meta('post', 'stop_lat',     ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'number']);
    register_meta('post', 'stop_lng',     ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'number']);
    register_meta('post', 'stop_notes',   ['object_subtype' => 'truck_location', 'single' => true, 'type' => 'string']);
});

The stop_lat and stop_lng fields matter for the map display and for the "get directions" link that opens Google Maps or Apple Maps depending on the visitor's device.

For the Google Calendar sync, we maintain our schedule in Google Calendar (we use it for our own operations anyway) and pull it into WordPress every two hours via a cron job:

// Schedule sync every 2 hours
add_action('wp', function() {
    if (!wp_next_scheduled('sync_truck_schedule')) {
        wp_schedule_event(time(), 'twicehourly', 'sync_truck_schedule');
    }
});

add_action('sync_truck_schedule', 'pull_google_calendar_events');

function pull_google_calendar_events() {
    $calendar_id  = urlencode(get_option('truck_calendar_id'));
    $api_key      = get_option('truck_google_api_key');
    $time_min     = urlencode(gmdate('c')); // now
    $time_max     = urlencode(gmdate('c', strtotime('+30 days')));

    $url = "https://www.googleapis.com/calendar/v3/calendars/{$calendar_id}/events"
         . "?key={$api_key}&timeMin={$time_min}&timeMax={$time_max}"
         . "&singleEvents=true&orderBy=startTime&maxResults=20";

    $response = wp_remote_get($url, ['timeout' => 15]);
    if (is_wp_error($response)) return;

    $data   = json_decode(wp_remote_retrieve_body($response), true);
    $events = $data['items'] ?? [];

    foreach ($events as $event) {
        $existing = get_posts([
            'post_type'  => 'truck_location',
            'meta_key'   => 'gcal_event_id',
            'meta_value' => $event['id'],
            'numberposts'=> 1,
        ]);

        $post_data = [
            'post_title'  => sanitize_text_field($event['summary'] ?? 'Location Stop'),
            'post_type'   => 'truck_location',
            'post_status' => 'publish',
        ];

        $post_id = $existing
            ? wp_update_post(array_merge($post_data, ['ID' => $existing[0]->ID]))
            : wp_insert_post($post_data);

        if ($post_id) {
            update_post_meta($post_id, 'gcal_event_id',  $event['id']);
            update_post_meta($post_id, 'stop_date',      substr($event['start']['dateTime'] ?? '', 0, 10));
            update_post_meta($post_id, 'stop_start',     $event['start']['dateTime'] ?? '');
            update_post_meta($post_id, 'stop_end',       $event['end']['dateTime']   ?? '');
            update_post_meta($post_id, 'stop_address',   $event['location']          ?? '');
        }
    }
}

Now when we add a stop to our Google Calendar — which we're doing anyway for our own planning — it shows up on the website within two hours automatically. No manual website updates. This eliminated the "stale schedule" problem entirely.


The Theme Layer: Why Food Trucks Need Different Templates

After building the schedule system, I needed a theme that could display it well. Generic restaurant themes are built around a static address and fixed hours. The template assumptions — "here's our location map" with a single pin, "here are our hours" with a weekly table — don't match how a food truck actually operates.

I tested three themes before landing on the Stego – Food Truck & Restaurant Theme. The deciding factors:

The homepage has a "Find Us Today" section slot that's designed to show dynamic schedule content — it's not a static address block. That single structural decision means I can output our next three stops in a visually prominent position without rebuilding the layout.

The menu display is set up for rotating and seasonal menus rather than a fixed menu assumed to be permanent. We change our offerings by season and by event type (weekend market menu differs from weekday lunch menu). A theme that shows one static menu doesn't fit that reality.

The mobile layout is genuinely optimized for the food truck use case. Most people finding a food truck are on a phone, standing somewhere, deciding whether to walk to where we are. The Stego mobile layout puts the map and the time-until-closing front and center on mobile. I didn't have to override a dozen CSS rules to achieve this — it's the default mobile hierarchy.


Progressive Web App: Offline Menu Access That Costs Nothing

One feature I didn't plan to build but ended up implementing: a basic Progressive Web App configuration that caches the menu for offline access.

Here's why it matters specifically for food trucks: our typical customer is at an outdoor market or festival. Cell coverage at these events can be terrible — 2,000 people in a field all hitting the same cell tower. A customer who found us, navigated to our website, and then tries to view the menu while standing in front of the truck should not see a loading spinner or an error.

The PWA implementation caches the menu page so it loads from the device's local storage even with no connection:

// service-worker.js — registered in theme footer
const CACHE_VERSION = 'truck-v1';
const CACHED_URLS = [
    '/',
    '/menu/',
    '/wp-content/themes/stego-child/style.css',
    '/wp-content/themes/stego-child/assets/js/main.js',
];

self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_VERSION).then(cache => cache.addAll(CACHED_URLS))
    );
});

self.addEventListener('fetch', event => {
    // Network first for schedule (needs fresh data)
    // Cache first for menu (rarely changes during a day)
    const isSchedule = event.request.url.includes('/schedule');
    const isMenu     = event.request.url.includes('/menu');

    if (isMenu) {
        event.respondWith(
            caches.match(event.request).then(cached =>
                cached || fetch(event.request).then(response => {
                    caches.open(CACHE_VERSION).then(cache =>
                        cache.put(event.request, response.clone())
                    );
                    return response;
                })
            )
        );
    } else if (isSchedule) {
        event.respondWith(
            fetch(event.request).catch(() => caches.match(event.request))
        );
    }
});

The strategy differs by page type: the menu uses cache-first (because it doesn't change during the day), while the schedule uses network-first with cache fallback (because we want fresh data but will show cached data if offline). This prevents the failure mode where a customer sees an error while standing in front of our truck because the festival WiFi is overloaded.

I register the service worker in the theme's footer.php:

<script>
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js')
        .then(reg => console.log('SW registered'))
        .catch(err => console.error('SW error:', err));
}
</script>

No plugin required. About 40 lines of code. The result: our menu page scores 100 on Lighthouse's PWA audit and loads in under 100ms on repeat visits even on poor connections.


Local SEO for a Business With No Fixed Address

This is the part that took me longest to figure out because all the standard local SEO advice assumes you have a storefront address to optimize for. "Claim your Google Business Profile, fill in your address, get reviews at that location." None of that maps cleanly to a food truck.

Here's what actually works:

Google Business Profile for a service-area business

Food trucks qualify as "service area businesses" in GBP — businesses that serve customers at multiple locations rather than a fixed address. Setting this up correctly:

  1. In GBP, choose "I deliver goods and services to my customers" rather than "customers come to my business location"
  2. Set your service area as the geographic region you operate in (cities, not a radius from a home address)
  3. Keep the address field as your commercial kitchen or registered business address — this is required for verification but doesn't show publicly

This gives you a GBP presence that shows up for "[food type] food truck near me" searches without tying you to one location.

Event schema for individual location stops

Each of our scheduled stops gets Event schema:

{
    "@context": "https://schema.org",
    "@type": "FoodEvent",
    "name": "Stego Truck @ Riverside Saturday Market",
    "startDate": "2025-08-16T10:00:00-07:00",
    "endDate":   "2025-08-16T14:00:00-07:00",
    "location": {
        "@type": "Place",
        "name": "Riverside Saturday Market",
        "address": {
            "@type": "PostalAddress",
            "streetAddress": "4500 Market Street",
            "addressLocality": "Riverside",
            "addressRegion": "CA"
        }
    },
    "organizer": {
        "@type": "FoodEstablishment",
        "name": "Our Truck Name",
        "url": "https://ourtruck.com"
    }
}

I generate this schema dynamically from each truck_location post via a WordPress hook that outputs JSON-LD in the page head. When Google indexes these individual event pages, they can appear in Google Events search results — a traffic source most food trucks never touch.

Neighborhood and market name targeting in content

We write short posts (400-600 words) for every recurring market or venue we visit regularly: "Our Friday setup at Eastside Night Market," "What to expect from our downtown lunch spot." These are findable by people searching for food options at those specific venues and capture intent we'd miss without them.


Email List: The Channel That Actually Owns the Audience

The Instagram reach problem I described at the start — algorithm suppression of location posts — taught me the lesson that every platform-dependent business learns eventually: you don't own your audience on someone else's platform.

We started collecting email addresses at the truck. A simple sign at the order window: "Get our weekly schedule by email." QR code that goes to a subscribe page. In the first month we collected 340 addresses from people actively requesting location updates. These are our most loyal customers — the ones who came, liked us, and want to keep finding us.

The weekly email goes out Sunday night with our schedule for the coming week. Open rate: 62%. Significantly higher than our Instagram organic reach. These people opted in specifically to receive location information — they're exactly the audience we want.

On the WordPress side, I use FluentCRM (which has a free tier that covers our volume) to manage the list and send the weekly email. The schedule content gets pulled via a shortcode that queries our truck_location post type for the coming week:

add_shortcode('next_week_schedule', function() {
    $next_week_start = date('Y-m-d', strtotime('next Monday'));
    $next_week_end   = date('Y-m-d', strtotime('next Sunday'));

    $stops = get_posts([
        'post_type'   => 'truck_location',
        'numberposts' => 10,
        'meta_query'  => [[
            'key'     => 'stop_date',
            'value'   => [$next_week_start, $next_week_end],
            'compare' => 'BETWEEN',
            'type'    => 'DATE',
        ]],
        'meta_key'    => 'stop_date',
        'orderby'     => 'meta_value',
        'order'       => 'ASC',
    ]);

    $output = '<ul class="week-schedule">';
    foreach ($stops as $stop) {
        $date    = get_post_meta($stop->ID, 'stop_date',    true);
        $start   = get_post_meta($stop->ID, 'stop_start',   true);
        $address = get_post_meta($stop->ID, 'stop_address', true);
        $output .= sprintf(
            '<li><strong>%s %s</strong> — %s</li>',
            date('l', strtotime($date)),
            date('g:ia', strtotime($start)),
            esc_html($address)
        );
    }
    $output .= '</ul>';
    return $output;
});

This shortcode goes into the email template. The schedule updates automatically from the Google Calendar sync, so the email content is always current even if I set up the FluentCRM campaign two weeks in advance.


What We Spent and What It Got Us

Item Cost
VPS hosting (DigitalOcean) $12/month
Domain $14/year
Theme ~$60 one-time
FluentCRM (free tier) $0
Google Maps API ~$0 (within free tier for our volume)
Development time (mine) ~40 hours initial build

The website cost us about $800 all-in to build, including my time valued at market rate. Monthly running cost: $12.

Three months after launch, our schedule page was getting 800+ visits per month from organic search. People searching for food trucks at specific markets and events in our city were finding our individual location event pages. The email list crossed 1,000 subscribers. On-site ordering (we added a pre-order pickup option via WooCommerce for busy events) started generating $400-600/month in advance sales.

The Instagram algorithm problem hasn't gone away. But it doesn't matter as much anymore, because the people who want to find us have a way to find us that doesn't depend on platform decisions we can't control.


On Theme and Plugin Sourcing

For food truck operators or developers building for them, the theme and plugin decisions don't need to be expensive to be correct. The Stego – Food Truck & Restaurant Theme handled the structural requirements for this specific use case well. For comparing alternatives in the restaurant and food service space, the WooCommerce Themes Collection has enough variety to find options suited to different food business types.

For plugin sourcing — particularly for operators building their own Google Calendar sync, email marketing integration, or pre-order system — evaluating GPL-licensed options through Premium WordPress Plugins is a practical first step before paying full retail on every component.


The WordPress documentation on custom post types, meta queries, and the wp-cron scheduling system used in the Google Calendar sync above is maintained at the WordPress Developer Handbook — the most reliable reference for understanding how these APIs behave in production.

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:109
关注标签:0
加入于:2025-12-14