Skip to main content

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:
package.json
{
  "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.
1

Create .env file

Create a .env file in the project root:
touch .env
2

Add Firebase configuration

Add your Firebase project credentials:
.env
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.
3

Get Firebase credentials

To get these values:
  1. Go to Firebase Console
  2. Select your project (or create a new one)
  3. Click the gear icon → Project Settings
  4. Scroll to “Your apps” and select your web app
  5. 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

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

1

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)
2

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: "..."
}
3

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

1

Fetch local image

Fetch the image file from the public directory:
fetch('/public/toDoList.webp')
  .then((response) => response.blob())
2

Create storage reference

Create a reference to where the image will be stored:
const imageRef = ref(data, 'images/toDoList.webp')
3

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:
1

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.
2

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')
3

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.
4

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

1

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
2

Optimize images before upload

Resize and compress images before uploading:
# Resize to max 1200px width
cwebp -resize 1200 0 input.png -o output.webp
3

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
4

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
}
5

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

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
Problem: Can’t upload or download imagesSolutions:
  • Check Firebase Storage rules
  • Verify storage bucket name in config
  • Ensure Firebase project has Storage enabled
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

Build docs developers (and LLMs) love