Place all static assets (images, fonts, PDFs, icons) in the /public folder of your Lovable project. Files in /public are served at the site root — so /public/logo.png becomes available at yourdomain.com/logo.png. Upload files via GitHub (commit to the /public folder), Dev Mode (drag and drop), or Supabase Storage for user-uploaded content. Always reference assets with root-relative paths starting with / to avoid broken links on nested routes.
Why static asset management in Lovable requires specific file placement
Lovable projects are built with Vite, which has a specific way of handling static files. There are two places you can put assets, and each works differently. The /public folder is for files that should be served as-is, without any processing. Files placed here are copied directly to the build output and accessible from the root URL. This is the right place for images, fonts, PDFs, favicons, and any file you want to reference by a fixed URL. The /src folder is for files that Vite processes during the build — like TypeScript files, CSS modules, and images you import directly into React components. When you import an image from /src, Vite adds a content hash to the filename for cache busting. This is useful for component-specific assets but unnecessary for most static files. The most common mistake is placing images in /src/assets and then referencing them with wrong paths in JSX. Another frequent issue is using relative paths (like 'images/logo.png' without a leading /) which break when the user navigates to a nested route like /dashboard/settings — the browser looks for images/logo.png relative to that route instead of the site root.
- Files placed in /src instead of /public — Vite processes them differently than expected
- Relative paths used instead of root-relative paths — breaks on nested routes
- Large files not uploaded to Supabase Storage — bloating the GitHub repo and build
- Binary files (images, fonts) added via AI prompt — Lovable cannot create binary files, only text
- Asset path works in preview but breaks after deployment to Vercel or Netlify
Error messages you might see
GET /src/assets/logo.png 404 (Not Found)You are referencing a file in /src/assets by URL path, but Vite does not serve /src files directly. Either move the file to /public or use a JavaScript import statement to let Vite handle the asset.
Module not found: Can't resolve './images/hero.png'A React component is trying to import an image that does not exist at the specified path. Check the exact filename (case-sensitive) and verify the file is in the correct folder relative to the importing component.
Error: this exceeds GitHub's file size limit of 100.00 MBA file you are trying to commit to GitHub is too large. GitHub has a 100 MB per-file limit. Use Supabase Storage for large files (videos, high-res images, datasets) instead of committing them to the repository.
Before you start
- A Lovable project open in the editor
- The static files (images, fonts, documents) ready on your computer
- GitHub connected if uploading via GitHub (Settings → Connectors → GitHub)
- Dev Mode access (paid plans) if uploading via the built-in code editor
How to fix it
Upload files to the /public folder via GitHub
GitHub is the most reliable way to add binary files (images, fonts) that Lovable's AI cannot create
Upload files to the /public folder via GitHub
GitHub is the most reliable way to add binary files (images, fonts) that Lovable's AI cannot create
Connect your Lovable project to GitHub (Settings → Connectors → GitHub → Connect project). Open your repository on GitHub. Navigate to the /public folder. Click Add file → Upload files. Drag your images, fonts, or other assets and commit them. The files sync back to Lovable automatically. Once in /public, they are accessible at the root URL: /public/logo.png becomes /logo.png in your app. This is the recommended method for adding images, custom fonts, and other binary assets.
<!-- WRONG: referencing a file that does not exist in /public --><img src="images/hero.jpg" alt="Hero" /><!-- CORRECT: file uploaded to /public/images/hero.jpg --><img src="/images/hero.jpg" alt="Hero" />Expected result: The image loads correctly in both the Lovable preview and on your published site.
Upload files directly via Dev Mode
Dev Mode lets you drag-and-drop files without leaving the Lovable editor
Upload files directly via Dev Mode
Dev Mode lets you drag-and-drop files without leaving the Lovable editor
If you have a paid Lovable plan, click the + button next to Preview and open Dev Mode (the Code panel). In the file tree on the left, navigate to the /public folder. You can drag and drop files from your computer directly into the file tree. The files are added to your project immediately and accessible at root-relative URLs. You can also create subfolders like /public/images or /public/fonts to keep things organized. After uploading, reference the files using root-relative paths in your components.
1// After uploading hero.jpg to /public/images/ via Dev Mode2// Reference it in any React component:3function HeroSection() {4 return (5 <div className="relative w-full h-96">6 <img7 src="/images/hero.jpg"8 alt="Hero banner"9 className="w-full h-full object-cover"10 />11 </div>12 );13}Expected result: The uploaded file appears in the Dev Mode file tree under /public and renders correctly in the preview.
Use Vite imports for component-specific assets in /src
Vite adds content hashes to imported assets for automatic cache busting
Use Vite imports for component-specific assets in /src
Vite adds content hashes to imported assets for automatic cache busting
For images tightly coupled to a specific component (like an icon used only in one button), you can place them in /src and import them as JavaScript modules. Vite processes these imports during build — renaming the file with a content hash and optimizing it. Use this approach for small component-specific assets. For larger shared assets (hero images, logos, fonts), the /public folder is still the better choice.
// WRONG: URL path reference to a /src file — will 404function Logo() { return <img src="/src/assets/logo.svg" alt="Logo" />;}// CORRECT: JavaScript import lets Vite handle the assetimport logoSvg from '@/assets/logo.svg';function Logo() { return <img src={logoSvg} alt="Logo" />;}Expected result: The image loads in both preview and production. Vite handles the file path and cache busting automatically.
Use Supabase Storage for user-uploaded and large files
Large files and user uploads should not live in the Git repository — they bloat builds and hit GitHub size limits
Use Supabase Storage for user-uploaded and large files
Large files and user uploads should not live in the Git repository — they bloat builds and hit GitHub size limits
For files that users upload (profile pictures, documents) or large static files (videos, high-res images over 5 MB), use Supabase Storage instead of the /public folder. In the Cloud tab, go to Storage to create a bucket. Then prompt Lovable to add a file upload component that stores files in the bucket and retrieves public URLs. This keeps your repository small and fast while giving you CDN-served URLs for large assets. If setting up Storage with proper access controls requires changes across multiple components, RapidDev's engineers have implemented this pattern across 600+ Lovable projects.
// WRONG: Storing large user files in /public via code// This bloats the repository and does not scale// CORRECT: Upload to Supabase Storage and get a public URLimport { supabase } from '@/integrations/supabase/client';async function uploadFile(file: File) { const fileName = `${Date.now()}-${file.name}`; const { data, error } = await supabase.storage .from('uploads') // your bucket name .upload(fileName, file); if (error) throw error; // Get the public URL for the uploaded file const { data: urlData } = supabase.storage .from('uploads') .getPublicUrl(fileName); return urlData.publicUrl;}Expected result: Files are stored in Supabase Storage with CDN-served URLs. The repository stays small and builds remain fast.
Complete code example
1import { useState } from 'react';2import { supabase } from '@/integrations/supabase/client';34// Demonstrates both /public assets and Supabase Storage usage5export function ImageGallery() {6 const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);78 // Static asset from /public folder — root-relative path9 const staticLogo = '/images/logo.png';1011 // Handle user file upload to Supabase Storage12 async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {13 const file = e.target.files?.[0];14 if (!file) return;1516 const fileName = `gallery/${Date.now()}-${file.name}`;17 const { error } = await supabase.storage18 .from('uploads')19 .upload(fileName, file);2021 if (error) {22 console.error('Upload failed:', error.message);23 return;24 }2526 const { data } = supabase.storage27 .from('uploads')28 .getPublicUrl(fileName);2930 setUploadedUrl(data.publicUrl);31 }3233 return (34 <div className="p-6 space-y-6">35 {/* Static asset from /public */}36 <img src={staticLogo} alt="Logo" className="h-12" />3738 {/* User upload to Supabase Storage */}39 <div>40 <label className="block text-sm font-medium mb-2">41 Upload an image42 </label>43 <input type="file" accept="image/*" onChange={handleUpload} />44 </div>4546 {uploadedUrl && (47 <img48 src={uploadedUrl}49 alt="Uploaded"50 className="max-w-md rounded-lg shadow"51 />52 )}53 </div>54 );55}Best practices to prevent this
- Place static assets in /public and reference them with root-relative paths starting with / — this works on all routes
- Use GitHub uploads for binary files (images, fonts, PDFs) that Lovable's AI cannot create from code
- Use Supabase Storage for user-uploaded content and large files over 5 MB to keep the repository small
- Organize /public with subfolders (/public/images, /public/fonts) to keep assets manageable
- Use JavaScript imports (import logo from '@/assets/logo.svg') for small component-specific assets in /src
- Optimize images before uploading — compress PNGs with TinyPNG and use WebP format where possible for faster loading
- Avoid spaces and special characters in filenames — use hyphens (hero-banner.jpg) for compatibility across platforms
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Lovable (lovable.dev) project using Vite + React + TypeScript. I need help organizing and referencing static assets. Here is my current project structure: [paste your file tree or describe your current asset locations] I have these assets to add: [list your images, fonts, or files] Please: 1. Tell me which files should go in /public vs /src/assets 2. Show the correct way to reference each file in React components 3. Show how to set up Supabase Storage for user-uploaded files 4. Explain why my images might break on nested routes 5. Create an optimized folder structure for my assets
Review my project's static asset references. Move any images or files incorrectly placed in /src that should be in /public. Update all asset references to use root-relative paths starting with / so they work on nested routes. For any img tags using relative paths like 'images/logo.png', change them to '/images/logo.png'. If there are large assets that should use Supabase Storage instead, set up a Storage bucket and create an upload utility function.
Frequently asked questions
Where do I put images in a Lovable project?
Place images in the /public folder. Files in /public are served at the site root — so /public/images/hero.jpg is accessible at /images/hero.jpg in your app. Upload via GitHub (commit to /public) or Dev Mode (drag and drop into the file tree). For user-uploaded images, use Supabase Storage instead.
How do I upload files to Lovable without a terminal?
There are two methods: (1) Connect GitHub and upload files directly to the /public folder in your repository — they sync back automatically. (2) Use Dev Mode (paid plans) to drag and drop files into the file tree. Both methods work without any terminal commands.
Why do my images break on certain pages but work on the homepage?
You are likely using relative paths like 'images/logo.png' instead of root-relative paths like '/images/logo.png'. Without the leading slash, the browser looks for the file relative to the current route. On /dashboard, it tries /dashboard/images/logo.png which does not exist. Always start paths with /.
What is the difference between /public and /src for assets in Lovable?
Files in /public are copied directly to the build output and served at root URLs without processing. Files in /src are processed by Vite — imported assets get content-hashed filenames for cache busting. Use /public for most static assets and /src imports only for small component-specific images.
How do I add custom fonts to a Lovable project?
Upload font files (.woff2, .woff, .ttf) to /public/fonts via GitHub or Dev Mode. Then add @font-face rules in your CSS (or Tailwind config) referencing the fonts with root-relative paths like /fonts/MyFont.woff2. Alternatively, use Google Fonts by adding a link tag to index.html.
How do I handle large files like videos in Lovable?
Do not commit large files (over 5 MB) to your repository — GitHub has a 100 MB file limit and large files slow down builds. Use Supabase Storage instead: create a storage bucket in Cloud tab → Storage, upload the file, and use the public URL in your components.
What if I cannot get static assets working correctly myself?
Asset management across /public, Vite imports, Supabase Storage, and deployment environments can be confusing with generated code. RapidDev's engineers have organized assets across 600+ Lovable projects and can set up the correct structure for your specific needs.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation