Debug School

rakesh kumar
rakesh kumar

Posted on

How to Reuse a Shared Navbar and Assets Between Two Laravel Microservices

When building a microservice-based web application, one common challenge is maintaining the same header, footer, theme, CSS, and JavaScript across multiple services.

For example, in the HolidayLandmark project, we had separate Laravel microservices such as:

The main public website was inside:

/opt/lampp/htdocs/holiday-new/holidaylandmark
Enter fullscreen mode Exit fullscreen mode

And the profile microservice was inside:

/opt/lampp/htdocs/holiday-new/profile
Enter fullscreen mode Exit fullscreen mode

The requirement was simple:

The profile microservice should reuse the same navbar, footer, Tailwind CSS, Alpine.js, and Vite assets from the main holidaylandmark service.

This blog explains how to implement shared navbar assets between two Laravel microservices and how to solve common production issues like missing views, missing CSS, wrong asset paths, Apache alias issues, and session permission errors.

Why Shared Navbar and Assets Are Needed

In a microservice architecture, every service may have its own Laravel application. If each service maintains its own navbar and footer, the following problems happen:

  1. UI inconsistency between services
  2. Duplicate navbar/footer code
  3. Extra maintenance work
  4. Different CSS behavior across services
  5. Broken user experience during navigation
  6. Higher chance of design drift

A better approach is to keep the navbar, footer, theme CSS, and JavaScript in one main public service and reuse them in other services.

In this example:

holidaylandmark = main public website
profile = profile microservice
Enter fullscreen mode Exit fullscreen mode

The profile microservice will reuse navbar/footer views and compiled assets from the main public website.

Final Folder Structure

The production folder structure looks like this:

/opt/lampp/htdocs/holiday-new/
├── holidaylandmark
│   ├── resources/views/partials/header.blade.php
│   ├── resources/views/partials/footer.blade.php
│   ├── build
│   │   ├── manifest.json
│   │   └── assets
│   │       ├── app-l3B943OL.css
│   │       └── app-CZm0HQoV.js
│   └── index.php
│
├── profile
│   ├── app/Support/SharedAssets.php
│   ├── config/view.php
│   ├── resources/views/layouts/app.blade.php
│   └── index.php

Enter fullscreen mode Exit fullscreen mode

The main service contains the shared Blade partials and compiled Vite build assets.

The profile service loads those shared files.

Step 1: Share Views Between Services

The profile microservice needs to load Blade partials from the main public website.

For example, this line inprofile/resources/views/layouts/app.blade.php:

@include('partials.header')
Enter fullscreen mode Exit fullscreen mode

will normally search only inside:

profile/resources/views/partials/header.blade.php

But our actual header exists in:

holidaylandmark/resources/views/partials/header.blade.php
Enter fullscreen mode Exit fullscreen mode

So we need to tell Laravel to also look inside the shared public-site view folder.

Open:

profile/config/view.php

Use this:

<?php

$sharedViewsPath = env(
    'PUBLIC_SITE_VIEWS_PATH',
    realpath(base_path('../holidaylandmark/resources/views')) ?: null
);

return [

    'paths' => array_values(array_filter([
        resource_path('views'),
        $sharedViewsPath,
    ])),

    'compiled' => env(
        'VIEW_COMPILED_PATH',
        realpath(storage_path('framework/views'))
    ),

];
Enter fullscreen mode Exit fullscreen mode

Now Laravel will first check its own profile views and then fall back to the shared views from the main holidaylandmark service.

Step 2: Add Shared View Path in .env

In the profile microservice .env file, add:

PUBLIC_SITE_VIEWS_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/resources/views
Enter fullscreen mode Exit fullscreen mode

This makes the path production-safe and avoids hardcoding everything inside PHP files.

After changing .env, clear Laravel cache:

cd /opt/lampp/htdocs/holiday-new/profile

php artisan optimize:clear
php artisan config:clear
php artisan view:clear
php artisan route:clear
Enter fullscreen mode Exit fullscreen mode

Step 3: Include Shared Header and Footer in Profile Layout

Open:

profile/resources/views/layouts/app.blade.php

Example layout:

<!DOCTYPE html>
<html lang="en" data-theme="holidaylandmark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'HolidayLandmark')</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">

    {!! \App\Support\SharedAssets::publicSiteBundle() !!}
</head>

<body class="min-h-screen bg-cream text-ink font-sans">
    @include('partials.header')

    <main id="main-content">
        @yield('content')
    </main>

    @include('partials.footer')
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The important line is:

{!! \App\Support\SharedAssets::publicSiteBundle() !!}
Enter fullscreen mode Exit fullscreen mode

This line loads the shared CSS and JavaScript from the main public website.

Step 4: Create SharedAssets.php

In the profile microservice, create:

profile/app/Support/SharedAssets.php
Enter fullscreen mode Exit fullscreen mode

Use this code:

<?php

namespace App\Support;

use Illuminate\Support\Facades\File;
use Illuminate\Support\HtmlString;

class SharedAssets
{
    public static function publicSiteBundle(): HtmlString
    {
        $manifestPath = env(
            'SHARED_ASSETS_MANIFEST_PATH',
            base_path('../holidaylandmark/build/manifest.json')
        );

        $assetBaseUrl = rtrim(
            env('SHARED_ASSETS_BASE_URL', '/build'),
            '/'
        );

        if (! is_file($manifestPath)) {
            return new HtmlString(
                '<!-- shared assets unavailable: manifest not found at ' . e($manifestPath) . ' -->'
            );
        }

        $manifest = json_decode(File::get($manifestPath), true) ?: [];

        $css = $manifest['resources/css/app.css']['file'] ?? null;
        $js  = $manifest['resources/js/app.js']['file'] ?? null;

        $html = '';

        if ($css) {
            $html .= '<link rel="stylesheet" href="' . $assetBaseUrl . '/' . e($css) . '">' . "\n";
        }

        if ($js) {
            $html .= '<script type="module" src="' . $assetBaseUrl . '/' . e($js) . '"></script>' . "\n";
        }

        return new HtmlString($html);
    }
}
Enter fullscreen mode Exit fullscreen mode

This file reads the Vite manifest.json from the main holidaylandmark service and generates correct CSS and JS links.

Step 5: Add Shared Asset Path in .env

In profile/.env, add:

SHARED_ASSETS_MANIFEST_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/build/manifest.json
SHARED_ASSETS_BASE_URL=/build
Enter fullscreen mode Exit fullscreen mode

If your production site always redirects to www, you can also use:

SHARED_ASSETS_BASE_URL=https://www.holidaylandmark.com/build
Enter fullscreen mode Exit fullscreen mode

Then clear cache:

cd /opt/lampp/htdocs/holiday-new/profile
Enter fullscreen mode Exit fullscreen mode
php artisan optimize:clear
php artisan config:clear
php artisan view:clear
php artisan route:clear
Enter fullscreen mode Exit fullscreen mode

Step 6: Understand the Vite Manifest

The main service has this file:

holidaylandmark/build/manifest.json
Enter fullscreen mode Exit fullscreen mode

Example:

{
  "resources/css/app.css": {
    "file": "assets/app-l3B943OL.css",
    "src": "resources/css/app.css",
    "isEntry": true
  },
  "resources/js/app.js": {
    "file": "assets/app-CZm0HQoV.js",
    "name": "app",
    "src": "resources/js/app.js",
    "isEntry": true
  }
}
Enter fullscreen mode Exit fullscreen mode

The generated CSS URL becomes:

/build/assets/app-l3B943OL.css
Enter fullscreen mode Exit fullscreen mode

The generated JS URL becomes:


/build/assets/app-CZm0HQoV.js
Enter fullscreen mode Exit fullscreen mode

Do not manually guess these filenames because Vite changes them after every build.

Always read from manifest.json.

Step 7: Fix Apache /build Asset URL

A common production problem is that this URL gives 404:

https://www.holidaylandmark.com/build/assets/app-l3B943OL.css
Enter fullscreen mode Exit fullscreen mode

This happens when Apache does not know that /build should point to:

/opt/lampp/htdocs/holiday-new/holidaylandmark/build
Enter fullscreen mode Exit fullscreen mode

Add this inside the correct Apache VirtualHost:

Alias /build "/opt/lampp/htdocs/holiday-new/holidaylandmark/build"

<Directory "/opt/lampp/htdocs/holiday-new/holidaylandmark/build">
    Options +FollowSymLinks -Indexes
    AllowOverride None
    Require all granted
</Directory>
Enter fullscreen mode Exit fullscreen mode

Important: use this syntax:

Options +FollowSymLinks -Indexes
Enter fullscreen mode Exit fullscreen mode

Do not write:

Options FollowSymLinks -Indexes
Enter fullscreen mode Exit fullscreen mode

That causes this Apache error:

Either all Options must start with + or -, or no Option may.

After editing Apache config, check syntax:

sudo /opt/lampp/bin/apachectl -t

If it says:

Syntax OK

restart Apache:

sudo /opt/lampp/lampp restart
Enter fullscreen mode Exit fullscreen mode

Now test CSS:

curl -k -I https://www.holidaylandmark.com/build/assets/app-l3B943OL.css
Enter fullscreen mode Exit fullscreen mode

Expected result:

HTTP/1.1 200 OK
Content-Type: text/css
Step 8: Example Apache Configuration

Here is an example Apache configuration for the HolidayLandmark setup:

Alias /build "/opt/lampp/htdocs/holiday-new/holidaylandmark/build"

<Directory "/opt/lampp/htdocs/holiday-new/holidaylandmark/build">
    Options +FollowSymLinks -Indexes
    AllowOverride None
    Require all granted
</Directory>

Alias /trips      /opt/lampp/htdocs/holiday-new/holidaylandmark
Alias /organizers /opt/lampp/htdocs/holiday-new/holidaylandmark

<Directory "/opt/lampp/htdocs/holiday-new/holidaylandmark">
    AllowOverride All
    Require all granted
    Options -MultiViews -Indexes
</Directory>

AliasMatch ^/country(/.*)?$               /opt/lampp/htdocs/holiday-new/country/index.php
AliasMatch ^/trip(/.*)?$                  /opt/lampp/htdocs/holiday-new/trip/index.php
AliasMatch ^/booking(/.*)?$               /opt/lampp/htdocs/holiday-new/booking/index.php
AliasMatch ^/payment(/.*)?$               /opt/lampp/htdocs/holiday-new/payment/index.php
AliasMatch ^/user(/.*)?$                  /opt/lampp/htdocs/holiday-new/user/index.php
AliasMatch ^/file(/.*)?$                  /opt/lampp/htdocs/holiday-new/file/index.php

AliasMatch ^/auth(/.*)?$                  /opt/lampp/htdocs/holiday-new/profile/index.php
AliasMatch ^/keycloak(/.*)?$              /opt/lampp/htdocs/holiday-new/profile/index.php
AliasMatch ^/become-organizer(/.*)?$      /opt/lampp/htdocs/holiday-new/profile/index.php
AliasMatch ^/tourist/profile(/.*)?$       /opt/lampp/htdocs/holiday-new/profile/index.php
AliasMatch ^/country-admin/profile(/.*)?$ /opt/lampp/htdocs/holiday-new/profile/index.php
AliasMatch ^/super-admin/profile(/.*)?$   /opt/lampp/htdocs/holiday-new/profile/index.php
AliasMatch ^/organizer/profile(/.*)?$     /opt/lampp/htdocs/holiday-new/profile/index.php

<Directory "/opt/lampp/htdocs/holiday-new">
    AllowOverride All
    Require all granted
    Options FollowSymLinks
</Directory>
Enter fullscreen mode Exit fullscreen mode

Step 9: Fix File and Folder Permissions

The build folder must be readable by Apache:

chmod -R 755 /opt/lampp/htdocs/holiday-new/holidaylandmark/build
chown -R daemon:daemon /opt/lampp/htdocs/holiday-new/holidaylandmark/build
Enter fullscreen mode Exit fullscreen mode

For XAMPP, Apache commonly runs as daemon.

To check Apache user:

ps aux | grep -E 'httpd|apache2' | grep -v root | head
Enter fullscreen mode Exit fullscreen mode

If Apache runs as www-data, use:

chown -R www-data:www-data /opt/lampp/htdocs/holiday-new/holidaylandmark/build
Enter fullscreen mode Exit fullscreen mode

Step 10: Common Error: View Not Found

Error:

View [partials.header] not found.
Enter fullscreen mode Exit fullscreen mode

Reason:

The profile microservice cannot find the shared header file.

Fix:

Make sure this file exists:

ls -lah /opt/lampp/htdocs/holiday-new/holidaylandmark/resources/views/partials/header.blade.php
Enter fullscreen mode Exit fullscreen mode

Then make sure profile/config/view.php includes:

'paths' => array_values(array_filter([
    resource_path('views'),
    env(
        'PUBLIC_SITE_VIEWS_PATH',
        realpath(base_path('../holidaylandmark/resources/views')) ?: null
    ),
])),
Enter fullscreen mode Exit fullscreen mode

And .env has:

PUBLIC_SITE_VIEWS_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/resources/views
Enter fullscreen mode Exit fullscreen mode

Then clear cache:

cd /opt/lampp/htdocs/holiday-new/profile

php artisan optimize:clear
php artisan config:clear
php artisan view:clear
Enter fullscreen mode Exit fullscreen mode

Step 11: Common Error: CSS Not Loading

Symptoms:

Page loads without design
Navbar appears broken
Huge SVG/logo appears
Only plain HTML is visible
“Skip to main content” appears at the top

Reason:

CSS file is not loading.

Check generated CSS link:

cat /opt/lampp/htdocs/holiday-new/holidaylandmark/build/manifest.json
Enter fullscreen mode Exit fullscreen mode

Then test the CSS URL:

curl -k -I https://www.holidaylandmark.com/build/assets/app-l3B943OL.css
Enter fullscreen mode Exit fullscreen mode

If it returns:

404 Not Found

then Apache /build alias is missing or wrong.

Fix:

Alias /build "/opt/lampp/htdocs/holiday-new/holidaylandmark/build"

<Directory "/opt/lampp/htdocs/holiday-new/holidaylandmark/build">
    Options +FollowSymLinks -Indexes
    AllowOverride None
    Require all granted
</Directory>
Enter fullscreen mode Exit fullscreen mode

Step 12: Common Error: Wrong Folder Name

In our case, one problem happened because the folder name was wrongly written as:

holidaylanmark

But the real folder name was:

holidaylandmark

This caused session and asset path errors.

To find wrong references:

cd /opt/lampp/htdocs/holiday-new
Enter fullscreen mode Exit fullscreen mode
grep -R "holidaylanmark" -n . \
--exclude-dir=vendor \
--exclude-dir=node_modules \
--exclude-dir=storage \
--exclude-dir=.git 2>/dev/null
Enter fullscreen mode Exit fullscreen mode
Replace wrong values carefully:
Enter fullscreen mode Exit fullscreen mode
sed -i 's|holidaylanmark|holidaylandmark|g' holidaylandmark/.env
sed -i 's|holidaylanmark|holidaylandmark|g' profile/.env
sed -i 's|holidaylanmark|holidaylandmark|g' user/.env
Enter fullscreen mode Exit fullscreen mode

Then clear cache in each Laravel app.

Step 13: Common Error: Session Folder Permission

Error:

file_put_contents(.../_shared/sessions/...): Failed to open stream: Permission denied

Fix:

mkdir -p /opt/lampp/htdocs/holiday-new/holidaylandmark/_shared/sessions

chmod -R 775 /opt/lampp/htdocs/holiday-new/holidaylandmark/_shared
chown -R daemon:daemon /opt/lampp/htdocs/holiday-new/holidaylandmark/_shared
Enter fullscreen mode Exit fullscreen mode

If Apache runs as www-data:

chown -R www-data:www-data /opt/lampp/htdocs/holiday-new/holidaylandmark/_shared
Enter fullscreen mode Exit fullscreen mode

In .env:


SESSION_DRIVER=file
SESSION_FILES_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/_shared/sessions
Enter fullscreen mode Exit fullscreen mode

Then clear cache:

php artisan optimize:clear
php artisan config:clear
Step 14: Common Error: Bootstrap Cache Not Writable

Error:

The /opt/lampp/htdocs/holiday-new/holidaylandmark/bootstrap/cache directory must be present and writable.

Fix:

cd /opt/lampp/htdocs/holiday-new/holidaylandmark

mkdir -p bootstrap/cache
chmod -R 775 bootstrap/cache
chown -R daemon:daemon bootstrap/cache

Also fix storage:

mkdir -p storage/framework/cache/data
mkdir -p storage/framework/sessions
mkdir -p storage/framework/views
mkdir -p storage/logs

chmod -R 775 storage bootstrap/cache
chown -R daemon:daemon storage bootstrap/cache
Step 15: Final Production Checklist

Before going live, verify these things:

1. Shared view exists

ls -lah /opt/lampp/htdocs/holiday-new/holidaylandmark/resources/views/partials/header.blade.php

2. Manifest exists

ls -lah /opt/lampp/htdocs/holiday-new/holidaylandmark/build/manifest.json

3. CSS file exists

ls -lah /opt/lampp/htdocs/holiday-new/holidaylandmark/build/assets/

4. CSS URL works

curl -k -I https://www.holidaylandmark.com/build/assets/app-l3B943OL.css

5. Apache syntax is OK

sudo /opt/lampp/bin/apachectl -t

6. Laravel cache is cleared

cd /opt/lampp/htdocs/holiday-new/profile
php artisan optimize:clear
php artisan config:clear
php artisan view:clear

Expected CSS response:

HTTP/1.1 200 OK
Content-Type: text/css
Best Practice Recommendation

For long-term stability, avoid hardcoding paths inside PHP files.

Use .env values:

PUBLIC_SITE_VIEWS_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/resources/views
SHARED_ASSETS_MANIFEST_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/build/manifest.json
SHARED_ASSETS_BASE_URL=https://www.holidaylandmark.com/build
SESSION_FILES_PATH=/opt/lampp/htdocs/holiday-new/holidaylandmark/_shared/sessions

This keeps the setup flexible for local, staging, and production environments.

Conclusion

Sharing navbar, footer, CSS, and JavaScript between Laravel microservices is a clean way to maintain UI consistency across a platform.

The main idea is:

Keep shared views in one main service
Allow other microservices to load those views using config/view.php
Load shared Vite assets using manifest.json
Make /build publicly accessible through Apache Alias
Keep production paths configurable using .env
Clear Laravel cache after every config change

With this approach, multiple Laravel microservices ca

Top comments (0)