Overview
Portfolio Moretto uses Firebase to store and manage project data and images. The integration includes Firestore for the database and Firebase Storage for hosting project images.
Firebase Setup
Install Firebase SDK
Firebase is already included in the project dependencies:
{
"dependencies" : {
"firebase" : "^10.3.1" ,
"@firebase/storage" : "^0.11.2"
}
}
Environment Variables
Firebase requires several API keys and configuration values. These are stored in environment variables for security.
Create .env file
Create a .env file in the project root:
Add Firebase configuration
Add your Firebase project credentials: VITE_apiKey = AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXX
VITE_authDomain = your-project.firebaseapp.com
VITE_projectId = your-project-id
VITE_storageBucket = your-project.appspot.com
VITE_messagingSenderId = 123456789012
VITE_appId = 1:123456789012:web:abcdef123456
Never commit .env to version control! Make sure it’s in your .gitignore file.
Get Firebase credentials
To get these values:
Go to Firebase Console
Select your project (or create a new one)
Click the gear icon → Project Settings
Scroll to “Your apps” and select your web app
Copy the config values to your .env file
The VITE_ prefix is required for Vite to expose these variables to your client code.
Firebase Configuration
The Firebase SDK is initialized in src/components/db/data.js:
src/components/db/data.js
import { getStorage , ref , uploadBytes , getDownloadURL } from "@firebase/storage" ;
import { initializeApp } from "firebase/app" ;
import { addDoc , collection , getFirestore } from 'firebase/firestore'
const firebaseConfig = {
apiKey: import . meta . env . VITE_apiKey ,
authDomain: import . meta . env . VITE_authDomain ,
projectId: import . meta . env . VITE_projectId ,
storageBucket: import . meta . env . VITE_storageBucket ,
messagingSenderId: import . meta . env . VITE_messagingSenderId ,
appId: import . meta . env . VITE_appId ,
};
const app = initializeApp ( firebaseConfig );
export const db = getFirestore ( app )
export const data = getStorage ( app )
Configuration Breakdown
Environment Variable Access
Vite exposes environment variables through import.meta.env: // Access environment variables
const apiKey = import . meta . env . VITE_apiKey
Only variables prefixed with VITE_ are accessible in client code.
The initializeApp() function initializes Firebase with your config: const app = initializeApp ( firebaseConfig );
This must be called before using any Firebase services.
The file exports two Firebase services:
db - Firestore database instance
data - Firebase Storage instance (named data in this project)
Firestore Database
Firestore is used to store project information including titles, descriptions, links, and image URLs.
Database Structure
Projects are stored in a projects collection with the following structure:
{
title : "Project Name" ,
description : "Detailed project description" ,
type : "category" ,
img : "https://firebasestorage.googleapis.com/..." ,
githubLink : "https://github.com/..." ,
webLink : "https://example.com"
}
Adding Projects to Firestore
The agergarDocumento function handles adding new projects:
src/components/db/data.js
export async function agergarDocumento ( referencia ) {
let imageURL ;
let indiviudalImageRef = ref ( data , `images/ ${ referencia } .webp` )
let url = getDownloadURL ( indiviudalImageRef )
try {
await url . then (( res ) => { imageURL = res })
} catch ( error ) {
console . log ( error , "error here" )
}
let proyecto = {
title: "To Do List" ,
description: "Desarrollé una aplicación de lista de tareas..." ,
type: "varios" ,
img: imageURL ,
githubLink: "https://github.com/fedemoretto11/To-Do-List" ,
webLink: "https://fedemoretto11.github.io/To-Do-List/"
}
// Uncomment to add to Firestore:
// addDoc(collection(db, "projects"), proyecto)
// .then((res) => {
// console.log(res, "Docu subido correctamente")
// })
// .catch((err) => {
// console.error(err, "error de carga de documento")
// })
}
How It Works
Get image URL
First, it retrieves the download URL for the project image from Firebase Storage: let indiviudalImageRef = ref ( data , `images/ ${ referencia } .webp` )
let url = getDownloadURL ( indiviudalImageRef )
Create project object
Then it creates a project object with all the necessary fields: let proyecto = {
title: "Project Title" ,
description: "..." ,
type: "category" ,
img: imageURL ,
githubLink: "..." ,
webLink: "..."
}
Add to Firestore
Finally, it adds the document to the projects collection: addDoc ( collection ( db , "projects" ), proyecto )
This code is currently commented out. Uncomment it when you’re ready to add projects.
Using the Function
To add a new project:
import { agergarDocumento } from './components/db/data'
// Add a project (image reference name without .webp extension)
agergarDocumento ( "projectImageName" )
Make sure the image exists in Firebase Storage at images/projectImageName.webp before calling this function.
Firebase Storage
Firebase Storage is used to host project images in WebP format for optimal performance.
Storage Structure
storage-bucket/
└── images/
├── project1.webp
├── project2.webp
└── project3.webp
All project images are stored in the images/ folder with .webp extension.
Uploading Images
The subirFoto function handles image uploads:
src/components/db/data.js
const metadata = {
contentType: 'image/webp'
}
export function subirFoto () {
fetch ( '/public/toDoList.webp' )
. then (( response ) => response . blob ())
. then (( blob ) => {
uploadBytes ( indiviudalImageRef , blob , metadata )
. then (( snapshot ) => {
console . log ( "uploaded by Fede" , snapshot )
})
. catch (( err ) => {
console . error ( "not catch" , err )
})
})
. catch (( err ) => {
console . error ( err , "hay un mistake" )
})
}
Upload Process
Fetch local image
Fetch the image file from the public directory: fetch ( '/public/toDoList.webp' )
. then (( response ) => response . blob ())
Create storage reference
Create a reference to where the image will be stored: const imageRef = ref ( data , 'images/toDoList.webp' )
Upload with metadata
Upload the blob with proper metadata: const metadata = { contentType: 'image/webp' }
uploadBytes ( imageRef , blob , metadata )
Retrieving Image URLs
To get a download URL for an uploaded image:
import { ref , getDownloadURL } from "@firebase/storage" ;
import { data } from './components/db/data'
const imageRef = ref ( data , 'images/project.webp' )
getDownloadURL ( imageRef )
. then (( url ) => {
console . log ( 'Image URL:' , url )
// Use this URL in your img tags
})
. catch (( error ) => {
console . error ( 'Error getting URL:' , error )
})
Download URLs are permanent and can be cached. They include authentication tokens that allow public access to the files.
Working with Project Data
Complete Workflow
Here’s the complete process for adding a new project:
Prepare your image
Convert your project screenshot to WebP format: # Using cwebp (install from webp package)
cwebp input.png -o project.webp
Place it in the public/ directory temporarily.
Upload image to Storage
Create and call an upload function: import { ref , uploadBytes } from "@firebase/storage" ;
import { data } from './components/db/data'
async function uploadProjectImage ( fileName ) {
const response = await fetch ( `/public/ ${ fileName } ` )
const blob = await response . blob ()
const imageRef = ref ( data , `images/ ${ fileName } ` )
const metadata = { contentType: 'image/webp' }
await uploadBytes ( imageRef , blob , metadata )
console . log ( 'Upload complete!' )
}
uploadProjectImage ( 'project.webp' )
Add project to Firestore
Modify the agergarDocumento function with your project details: let proyecto = {
title: "Your Project Title" ,
description: "Your project description in Spanish" ,
type: "web" // or "varios", etc.
img : imageURL ,
githubLink: "https://github.com/username/repo" ,
webLink: "https://your-project.com"
}
Uncomment the addDoc call and run the function.
Verify in Firebase Console
Check the Firebase Console to ensure:
Image appears in Storage → images/
Document appears in Firestore → projects collection
Reading Projects
To fetch and display projects from Firestore:
import { collection , getDocs } from 'firebase/firestore'
import { db } from './components/db/data'
async function getProjects () {
const querySnapshot = await getDocs ( collection ( db , "projects" ))
const projects = []
querySnapshot . forEach (( doc ) => {
projects . push ({
id: doc . id ,
... doc . data ()
})
})
return projects
}
// Use in a React component
function ProjectList () {
const [ projects , setProjects ] = useState ([])
useEffect (() => {
getProjects (). then ( setProjects )
}, [])
return (
< div >
{ projects . map ( project => (
< div key = { project . id } >
< h2 > { project . title } </ h2 >
< img src = { project . img } alt = { project . title } />
< p > { project . description } </ p >
< a href = { project . githubLink } > GitHub </ a >
< a href = { project . webLink } > Live Demo </ a >
</ div >
)) }
</ div >
)
}
Security Rules
Firestore Rules
Set up security rules in Firebase Console → Firestore Database → Rules:
rules_version = '2' ;
service cloud . firestore {
match / databases / { database } / documents {
// Public read access for projects
match / projects / { project } {
allow read : if true ;
allow write : if false ; // Only allow writes from backend/admin
}
}
}
Storage Rules
Set up storage rules in Firebase Console → Storage → Rules:
rules_version = '2' ;
service firebase . storage {
match / b / { bucket } / o {
match / images / { imageId } {
allow read : if true ; // Public read access
allow write : if false ; // No client-side uploads
}
}
}
These rules allow public read access but prevent client-side writes. For production, manage content through a secure admin interface or backend.
Best Practices
Use WebP format
Always use WebP for images to reduce file size:
Smaller files = faster loading
Better compression than PNG/JPEG
Supported in all modern browsers
Optimize images before upload
Resize and compress images before uploading: # Resize to max 1200px width
cwebp -resize 1200 0 input.png -o output.webp
Cache download URLs
Firebase Storage URLs don’t change. Cache them to avoid repeated fetches: // Store URL in Firestore document
// Fetch once during upload, use many times
Handle errors gracefully
Always wrap Firebase calls in try-catch: try {
const url = await getDownloadURL ( imageRef )
} catch ( error ) {
console . error ( 'Failed to load image:' , error )
// Show fallback image
}
Use environment-specific configs
Create separate Firebase projects for development and production:
.env.development - Dev Firebase project
.env.production - Production Firebase project
Troubleshooting
Common Issues
Environment variables not working
Problem: import.meta.env.VITE_apiKey is undefinedSolutions:
Ensure variables are prefixed with VITE_
Restart dev server after changing .env
Check .env is in project root
Verify no quotes around values in .env
Storage permission denied
Problem: Can’t upload or download imagesSolutions:
Check Firebase Storage rules
Verify storage bucket name in config
Ensure Firebase project has Storage enabled
Firestore permission denied
Problem: Can’t read or write documentsSolutions:
Check Firestore security rules
Verify projectId in config
Check collection and document paths
Problem: Images return 404 or don’t displaySolutions:
Verify image exists in Storage console
Check file path matches exactly (case-sensitive)
Ensure CORS is configured if loading from different domain
Check image URL format is correct
Next Steps
Component Architecture Learn how components use Firebase data
Customization Customize your portfolio content
Firebase Docs Official Firebase documentation
Firestore Guide Deep dive into Firestore