Frontend application keeps failing when displaying images from Google Drive API

I have successfully uploaded files to Google Drive using their API and configured public sharing permissions. However, my frontend crashes whenever I attempt to render these images. This has been an ongoing issue for multiple days without any resolution.

import { google } from "googleapis";
import { NextRequest, NextResponse } from "next/server";
import { Readable } from "stream";

const SHEET_ID = process.env.SHEET_ID!;
const CLIENT_EMAIL = process.env.CLIENT_EMAIL!;
const PRIVATE_KEY = process.env.PRIVATE_KEY!.replace(/\\n/g, "\n");
const FOLDER_ID = process.env.FOLDER_ID!;

export async function POST(request: NextRequest) {
  try {
    const data = await request.formData();
    const username = data.get("username") as string;
    const userEmail = data.get("userEmail") as string;
    const content = data.get("content") as string;
    const uploadedFile = data.get("image") as File | null;

    if (!username || !userEmail || !content) {
      return NextResponse.json({ error: "Required fields missing" }, { status: 400 });
    }

    const credentials = new google.auth.JWT(
      CLIENT_EMAIL,
      undefined,
      PRIVATE_KEY,
      [
        "https://www.googleapis.com/auth/spreadsheets",
        "https://www.googleapis.com/auth/drive",
      ]
    );

    const sheetsApi = google.sheets({ version: "v4", auth: credentials });
    const driveApi = google.drive({ version: "v3", auth: credentials });

    let fileUrl = "";

    if (uploadedFile && uploadedFile.size > 0) {
      const fileBuffer = Buffer.from(await uploadedFile.arrayBuffer());

      const result = await driveApi.files.create({
        requestBody: {
          name: uploadedFile.name,
          parents: [FOLDER_ID],
        },
        media: {
          mimeType: uploadedFile.type,
          body: Readable.from(fileBuffer),
        },
        fields: "id",
      });

      const uploadedFileId = result.data.id;

      if (uploadedFileId) {
        await driveApi.permissions.create({
          fileId: uploadedFileId,
          requestBody: {
            role: "reader",
            type: "anyone",
          },
        });

        fileUrl = `https://drive.google.com/uc?export=view&id=${uploadedFileId}`;
      }
    }

    await sheetsApi.spreadsheets.values.append({
      spreadsheetId: SHEET_ID,
      range: "Data!A:D",
      valueInputOption: "USER_ENTERED",
      requestBody: {
        values: [[username, userEmail, content, fileUrl]],
      },
    });

    return NextResponse.json({ success: true }, { status: 200 });
  } catch (err) {
    return NextResponse.json({ error: "Upload failed" }, { status: 500 });
  }
}

I attempted to retrieve and display the images through a separate API endpoint:

import { google } from "googleapis";
import { NextResponse } from "next/server";

export async function GET() {
  const auth = new google.auth.JWT(
    process.env.CLIENT_EMAIL,
    undefined,
    process.env.PRIVATE_KEY!.replace(/\\n/g, "\n"),
    ["https://www.googleapis.com/auth/spreadsheets.readonly"]
  );

  const sheetsClient = google.sheets({ version: "v4", auth });
  const result = await sheetsClient.spreadsheets.values.get({
    spreadsheetId: process.env.SHEET_ID,
    range: "Data!A2:D",
  });

  const records = result.data.values || [];
  const formattedData = records.map((record) => ({
    username: record[0],
    email: record[1],
    content: record[2],
    imageLink: record[3],
  }));

  return NextResponse.json(formattedData);
}
"use client";
import { Box, Image, Stack } from "@mantine/core";
import { useEffect, useState } from "react";

interface DataRecord {
  username: string;
  email: string;
  content: string;
  imageLink: string;
}

export default function DisplayPage() {
  const [records, setRecords] = useState<DataRecord[]>([]);

  useEffect(() => {
    const loadData = async () => {
      const response = await fetch("/api/fetch-data");
      const result = await response.json();
      setRecords(result);
    };
    loadData();
  }, []);

  return (
    <Stack>
      {records.map((record, index) => (
        <Box key={index}>
          <Image
            width={250}
            height={250}
            src={record.imageLink}
            alt="user upload"
          />
        </Box>
      ))}
    </Stack>
  );
}

The Problem:

You’re experiencing the NG0203 inject() must be called from an injection context error in your Angular microfrontend architecture using native federation when navigating between remote applications. This indicates a problem with how your Angular services are being injected or accessed within your microfrontends.

:thinking: Understanding the “Why” (The Root Cause):

The NG0203 error arises because you’re attempting to use Angular’s dependency injection (inject()) outside of an appropriate context. Angular’s dependency injection system works within specific lifecycle hooks (like constructor or ngOnInit) and components. If you’re calling inject() from a static method, a callback function not directly tied to a component’s lifecycle, or in a location where Angular’s injector isn’t readily available, this error will occur. In the context of microfrontends, this issue is exacerbated because you have multiple independent modules, each with its own injector, making coordination of service access crucial.

:gear: Step-by-Step Guide:

  1. Review Injection Locations: Carefully examine where you are calling inject(). Ensure that all calls to inject() are placed within the constructor or ngOnInit lifecycle hooks of your Angular components within the microfrontends. Avoid injecting services within static methods, asynchronous callbacks (unless properly managed), or other functions that are not directly part of the component’s lifecycle.

  2. Verify Service Providers: Confirm that the services you are injecting are correctly declared in the providers array of the relevant NgModule within each microfrontend. Each microfrontend module should declare the services it requires independently. Make sure to avoid any unnecessary inheritance of providers from parent or shell applications.

  3. Inspect Module Structure: Check if each microfrontend is correctly bootstrapped with its own Angular injector. This is paramount in a microfrontend setup to avoid injection conflicts. Ensure that the remote modules are not unintentionally sharing or inheriting the shell application’s injector, which would cause injection issues.

  4. Angular’s runInInjectionContext() (Advanced): In cases where you absolutely must inject services outside of the typical lifecycle hooks (which should be avoided if possible), use Angular’s runInInjectionContext() function to explicitly create the necessary injection context. This advanced technique helps to manage injection in more complex scenarios outside the typical lifecycle.

:mag: Common Pitfalls & What to Check Next:

  • Incorrect Module Imports: Verify that you’ve correctly imported the necessary modules and services into each microfrontend. Missing imports can lead to errors during injection.
  • Lazy Loading: If you’re using lazy loading with your microfrontends, make sure that services required by the lazy-loaded modules are correctly provided within those modules.
  • Asynchronous Operations: Handle service injection within asynchronous operations with care. Incorrectly timed injections in Promises or Observables are common sources of this error.
  • Circular Dependencies: Rule out any circular dependencies between your services and modules, which can lead to unexpected injection behavior.

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!

what do u mean by “crashes”? any error msgs showing up in the console? have u tried adding error handling to the Image component - like onError props or checking if imageLink exists before rendering?

The Problem:

You’re successfully uploading images to Google Drive via their API and setting public sharing permissions, but your frontend crashes when trying to render these images. The issue has persisted for several days. Your frontend uses the Image component from @mantine/core to display images fetched from a separate API endpoint.

:thinking: Understanding the “Why” (The Root Cause):

The problem lies in the URL you’re using to access the images from Google Drive. The https://drive.google.com/uc?export=view&id=${uploadedFileId} URL, while allowing access, is unreliable for embedding directly in <img> tags. This endpoint often returns an HTML redirect instead of the raw image data, causing your frontend to crash or fail to display the image correctly. Using this method in a frontend context is prone to breakage.

:gear: Step-by-Step Guide:

  1. Update Your Image URL Generation: Change how you generate image URLs in your backend (/api/upload endpoint). Instead of using https://drive.google.com/uc?export=view&id=${uploadedFileId}, use the Google Drive thumbnail URL: https://drive.google.com/thumbnail?id=${uploadedFileId}&sz=w400-h400. This endpoint reliably returns the image data directly, suitable for embedding. This change ensures that your frontend receives a proper image and avoids the redirect issues. Update your backend code accordingly. The &sz=w400-h400 part sets the image size to 400x400 pixels; you can adjust this as needed.

  2. Verify Environment Variables: Double-check that your environment variables (SHEET_ID, CLIENT_EMAIL, PRIVATE_KEY, FOLDER_ID) are correctly set and accessible in both your API endpoints (/api/upload and /api/fetch-data). Incorrectly configured environment variables are a common cause of API failures.

  3. Implement Robust Error Handling: Add error handling to your frontend’s Image component. The onError prop in @mantine/core’s Image component allows you to gracefully handle image loading failures. This could include displaying a placeholder image, an error message, or logging the error to the console for debugging. Here’s an example:

<Image
  width={250}
  height={250}
  src={record.imageLink}
  alt="user upload"
  onError={(e) => {
    console.error("Image loading failed:", e);
    // Display placeholder or error message here
  }}
/>
  1. Check for Null or Empty Image Links: Before rendering the image, ensure record.imageLink is not null or an empty string. Add a conditional check to prevent errors when record.imageLink is missing a value:
{record.imageLink && (
  <Image
    width={250}
    height={250}
    src={record.imageLink}
    alt="user upload"
    onError={(e) => { /* ... error handling ... */ }}
  />
)}

:mag: Common Pitfalls & What to Check Next:

  • Authentication Issues: Verify that your Google Cloud service account credentials are correctly configured and have the necessary permissions (https://www.googleapis.com/auth/drive).
  • Quota Limits: Google Drive has API usage quotas. If you’re exceeding these limits, you’ll experience errors. Check your Google Cloud Console for usage statistics.
  • Network Issues: Rule out network problems by testing your API endpoints directly using tools like curl or Postman.

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!