TeachyLeaf library

What are you looking for?

Search practical tutorials and browser-based tools.

Laravel · Deployment fundamentals · Beginner

Laravel asset versioning and browser caching

Trace how TeachyLeaf appends a version to static asset URLs and learn what changing that version can—and cannot—do for cached files.

Who this is for: Laravel developers who serve CSS, JavaScript, or images from the public directory and want to understand a simple cache-busting mechanism.

What you will learn: how TeachyLeaf builds versioned asset URLs, stores and rotates the version value, protects the admin control, and verifies the output in feature tests.

Prerequisites: Basic Laravel routing, Blade, and browser-cache concepts.

Tested environment: Laravel Framework 13.16.1 and PHP 8.4.15. The feature-test configuration uses the array cache; production behavior depends on the deployed cache and browser/CDN configuration.

Why include a version in an asset URL?

A browser can reuse a previously downloaded stylesheet when its URL has not changed. If a new CSS file is deployed under the same URL, a visitor may continue to see an older cached copy for some time. A version query parameter creates a different URL after the version changes, which can prompt a fresh request when the browser and any intermediary cache use the full URL as their cache key.

TeachyLeaf's frontend layout loads the global stylesheets with @versionedAsset and page templates add their own stylesheets using the same directive:

<link rel="stylesheet"
      href="@versionedAsset('assets/css/style.css')">

The resulting URL follows this pattern:

https://example.test/assets/css/style.css?v=1

The domain above is illustrative. The application builds the actual URL for its configured host.

How the helper builds the URL

The versionedAsset Blade directive calls the AssetVersion service. It first makes the public asset URL, chooses ? when the URL has no query string or & when it already has one, and URL-encodes the version value:

$url = asset($path);
$separator = str_contains($url, '?') ? '&' : '?';

return $url . $separator . 'v=' . rawurlencode($this->current());

The current version comes from the Laravel cache key assets.version. If that key has not been set, the service uses ASSET_VERSION through config/assets.php, which defaults to 1:

return (string) Cache::get(
    'assets.version',
    config('assets.version', '1')
);

Rotate the version after an asset change

The authenticated admin page posts to the asset-version update route. The service generates a UUID and stores it in the cache indefinitely. Future calls to the directive then use that new value:

$version = Str::uuid()->toString();
Cache::forever('assets.version', $version);

In this project the change-version routes are inside the auth middleware group. The form uses a POST request and includes Laravel's CSRF token. Rotating the version changes generated URLs; it does not edit, upload, or deploy the CSS or JavaScript files themselves.

What versioning does not guarantee

  • A query-string change does not compile, minify, or validate an asset.
  • A browser or CDN may have its own cache rules. Check whether it includes query parameters in the cache key; do not assume every intermediary treats two query strings as different objects.
  • Rotating a URL does not erase older cached copies or force every visitor to request the asset immediately.
  • If multiple app instances serve pages, configure the cache so they see a consistent version value.
  • Update the asset file first, then rotate the version and verify that the rendered page uses the expected URL and appearance.

Verify the behavior with a feature test

AssetVersionTest sets a known version, requests a page, and checks that the generated stylesheet URL includes that value. Another test authenticates a user, rotates the version through the admin route, and checks that subsequent pages use the new version.

config(['assets.version' => 'test-version']);

$response = $this->withoutVite()->get('/');

$response->assertOk();
$response->assertSee(
    'assets/css/style.css?v=test-version',
    false
);

These tests check Laravel's generated HTML and protected route behavior. They do not measure browser caching or verify how a production CDN handles query strings.

Practice: update a versioned URL

A stylesheet URL already has a query parameter for its theme:

https://example.test/assets/css/site.css?theme=light

If the version value is release 2, add it to the URL without replacing the existing theme parameter. What separator should you use, and how should the space be encoded?

Show the answer

Use an ampersand because the URL already contains a query string, and encode the space in the version value:

https://example.test/assets/css/site.css?theme=light&v=release%202

This exercise illustrates URL construction only. It does not require access to TeachyLeaf's private source code or admin area.

Explore the implementation (optional)

If you have access to the TeachyLeaf source, compare the concepts above with AssetVersion and the frontend asset directive. This codebase reference is optional; the lesson and practice exercise are complete without it.

References

Continue learning

See how the feature tests verify versioned asset URLs, then read about reusable Blade components and the shared circuit-diagram styles. For the larger request path that renders a Blade page, follow the Laravel request lifecycle lesson.