Uncaught ReferenceError: $ is not defined - Laravel 9, Vite

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Solving the Mystery: Uncaught ReferenceError: $ is not defined in Laravel 9 with Vite

As senior developers working within the Laravel ecosystem, we frequently encounter subtle yet frustrating JavaScript errors, especially when mixing legacy libraries like jQuery with modern build tools like Vite. The error Uncaught ReferenceError: $ is not defined often signals a mismatch between how the library is loaded and how the consuming script expects the global scope to be populated.

This post dives deep into why this happens in a Laravel 9/Vite setup, explores the pitfalls of importing modules, and provides robust solutions for correctly integrating jQuery (or any external dependency) into your application.

Understanding the Conflict: Modules vs. Global Scope

The core issue stems from the difference between traditional script loading and modern ES Module imports used by bundlers like Vite.

When you use npm i jquery, you are installing the package, but how that package is consumed in your Blade view and bundled by Vite determines whether the global variable ($) is available where you expect it.

In your setup:

  1. You load assets via @vite(['resources/js/app.js']). This compiles resources/js/app.js into a bundle.
  2. Your view attempts to run <script>$(function(){ ... })</script>.
  3. The error occurs because, by default in a module-based environment, variables imported via import $ from 'jquery'; are scoped locally within that module and do not automatically attach themselves to the global window object unless explicitly told to do so or loaded via a specific script mechanism.

Your attempts—like using window.jQuery = $; or trying complex Rollup plugins—show you understand the problem, but they often introduce complexity where a simpler solution exists within the Laravel/Vite paradigm.

Solution 1: The Recommended Approach for Vite Assets (Asset-Based Loading)

For modern Laravel applications leveraging Vite, the most idiomatic way to handle JavaScript dependencies is to treat them as assets that are explicitly loaded into the HTML document, ensuring the dependency loads before the dependent script runs.

Instead of relying solely on ES Module imports within your main entry file (app.js), load jQuery separately and ensure it's available globally before the main application logic executes.

Step 1: Load jQuery via a Separate Script Tag

Include the jQuery library directly in your main Blade layout (resources/views/layouts/app.blade.php) before your Vite assets are loaded, or ensure it loads first within the <head> or <body>.

In your main layout file (or wherever you manage external scripts):

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <!-- ... other head content ... -->
    
    <!-- Load jQuery directly from a CDN or local asset -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

    <!-- Vite assets load after dependencies are available -->
    @vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>
<body>
    <!-- ... content ... -->
</body>
</html>

By loading jQuery this way, it becomes a globally available variable ($) immediately upon execution, solving the ReferenceError for scripts running directly in the HTML view (like your alert example).

Step 2: Handling Imports within app.js

If you still need to import jQuery inside your Vite entry point (resources/js/app.js), you should rely on standard ES module imports, as the browser will handle the global scope resolution when running the bundled script in the correct order defined by the HTML structure.

In resources/js/app.js:

// This import correctly brings jQuery into the module scope
import $ from 'jquery'; 

// Now, use it within your application logic
$(function(){
    alert('jQuery is successfully loaded and available!');
});

// Any other custom application code...

This approach aligns perfectly with how Vite manages asset dependencies. It avoids manual manipulation of window objects, which can lead to unpredictable behavior across different build environments. This principle of dependency management is crucial when building scalable applications on platforms like Laravel. For deeper architectural guidance on structuring your frontend within a framework, exploring resources from the official platform makes sense, similar to how robust systems are designed at laravelcompany.com.

Conclusion: Prioritizing Order and Simplicity

The Uncaught ReferenceError: $ is not defined is less about the library itself being broken and more about the execution order of your JavaScript files relative to when the browser expects global variables to be defined.

As a senior developer, the best practice here is simplicity and strict ordering: load external dependencies via standard <script> tags first, and then let your modern bundler (Vite) handle the bundling of your application logic. By adhering to this structure, you ensure that legacy code can coexist smoothly with modern tooling in your Laravel project.