Daniel Kliewer

Complete Guide: Building a High-Performance Quiz Platform with Next.js and Firebase Integration

10 min read
Next.jsFirebaseQuiz PlatformAuthenticationFirestoreReal-TimeIframe SecurityAdmin DashboardEducational TechnologyWeb Development

Image

Building a High-Performance Quiz Platform with Next.js and Firebase

Creating an online quiz platform can be challenging, especially when you need to handle user authentication, store scores, and display dynamic content. In this comprehensive guide, I'll walk you through how to optimize a Next.js quiz application that leverages Firebase for authentication and Firestore for data storage.

The Challenge of Quiz Applications

Many educational platforms struggle with performance issues, security vulnerabilities, and code maintainability when implementing quiz functionality. Whether you're building a learning management system, an educational app, or just a fun quiz site, these challenges can significantly impact user experience.

Let's explore how to streamline a Next.js quiz platform with Firebase integration to create a secure, fast, and maintainable solution.

1. Centralizing Firebase Initialization

One common mistake is initializing Firebase multiple times across different components. This not only affects performance but can also lead to unexpected behaviors.

The Solution: Single Firebase Instance

Create a dedicated firebase.js file to handle initialization once:

JavaScript
// firebase.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';

const firebaseConfig = {
  apiKey: 'YOUR_KEY',
  authDomain: 'your-app.firebaseapp.com',
  projectId: 'your-app',
  storageBucket: 'your-app.appspot.com',
  messagingSenderId: '123456789',
  appId: '1:123456789:web:abcdef123456789'
};

// Initialize Firebase only once
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);

export { auth, db };

By exporting the initialized auth and db instances, you can import them wherever needed without creating redundant Firebase connections.

2. Optimizing the Quiz Page Component

Your quiz page should efficiently handle quiz rendering and score saving without unnecessary re-renders or network calls.

Implementation Approach #1: Direct HTML Rendering

JavaScript
// pages/quiz/[id].js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { doc, setDoc } from 'firebase/firestore';
import { auth, db } from '../../firebase';

const QuizPage = ({ quizHtml }) => {
  const router = useRouter();
  const { id } = router.query;

  useEffect(() => {
    // Expose the saveScore function to the quiz content
    const saveScore = async (score) => {
      const user = auth.currentUser;
      if (user) {
        const userDoc = doc(db, 'grades', user.uid);
        await setDoc(
          userDoc,
          { [id]: { score, updatedAt: new Date() } },
          { merge: true }
        );
      }
    };

    window.saveScore = saveScore;
  }, [id]);

  return (
    
); }; export async function getStaticProps({ params }) { const quizHtml = await getQuizHTML(params.id); // Implement this function to fetch quiz HTML return { props: { quizHtml } }; } export async function getStaticPaths() { // Implement this function to generate paths for all quizzes return { paths: [ { params: { id: 'quiz1' } }, { params: { id: 'quiz2' } }, // Add more quizzes as needed ], fallback: false }; } export default QuizPage;

This approach works but has potential security risks due to the use of dangerouslySetInnerHTML.

3. Enhancing Security with Iframe Isolation

A more secure approach is to isolate quiz content within an iframe, preventing potential XSS attacks and providing better content separation.

Implementation Approach #2: Iframe Isolation

JavaScript
// pages/quiz/[id].js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { doc, setDoc } from 'firebase/firestore';
import { auth, db } from '../../firebase';

const QuizPage = ({ quizPath }) => {
  const router = useRouter();
  const { id } = router.query;

  useEffect(() => {
    const saveScore = async (score) => {
      const user = auth.currentUser;
      if (user) {
        const userDoc = doc(db, 'grades', user.uid);
        await setDoc(
          userDoc,
          { [id]: { score, updatedAt: new Date() } },
          { merge: true }
        );
      } else {
        // Handle unauthenticated user scenario
        console.log('User not authenticated. Score not saved.');
        router.push('/login?returnUrl=' + router.asPath);
      }
    };

    // Listen for messages from the iframe
    window.addEventListener('message', (event) => {
      if (event.data.type === 'saveScore') {
        saveScore(event.data.score);
      }
    });

    // Cleanup event listener
    return () => {
      window.removeEventListener('message', (event) => {
        if (event.data.type === 'saveScore') {
          saveScore(event.data.score);
        }
      });
    };
  }, [id, router]);

  return (
    

Quiz {id}