Search code examples
javascriptreactjshttpnext.js

Unable to redirect to a dynamic URL in Next.js


In my Next.js 14 app, I have a /research/page.tsx file where I am receiving a file as user input and sending it to a server-side API for further processing. Here’s the method that does this:

const handleFileDrop = async (file: File) => {
    // Convert file to blob
    const arrayBuffer = await file.arrayBuffer()
    const blob = new Blob([arrayBuffer])
    const paperId = await generateFileId(arrayBuffer)
    const paperTitle = file.name

    // Send file to api/upload for upload to S3
    try {
      const res = await fetch("/api/upload", {
        method: "POST",
        body: file,
      })
      if (!res.ok) throw new Error(await res.text())
      console.log(await res.json())
    } catch (error: any) {
      // handle error
    }
  }

On the server, the API at /api/upload/route.ts is meant to do a bunch of things with the file and once done, redirect the user to a new dynamically generated route:

/* eslint-disable import/prefer-default-export, @typescript-eslint/no-unused-vars */

import { NextRequest, NextResponse } from "next/server"
import generateFileId from "@/lib/generate-file-id"
import { PDFLoader } from "langchain/document_loaders/fs/pdf"
// import AWS from "aws-sdk"

// const s3 = new AWS.S3()

async function streamToArrayBuffer(stream: ReadableStream) {
  return new Uint8Array(await new Response(stream).arrayBuffer())
}

export async function POST(request: NextRequest) {
  const data = await request.body
  if (!data) return NextResponse.json({ success: false })
  const arrayBuffer = await streamToArrayBuffer(data)
  const blob = new Blob([arrayBuffer])
  const loader = new PDFLoader(blob)
  const pageLevelDocs = await loader.load()
  const pageCount = pageLevelDocs.length
  const paperId = await generateFileId(arrayBuffer)

  /*
  STEP 1: Upload file to S3
  STEP 2: Add reference to file in Postgres
  */
  console.log("test")
  return NextResponse.redirect(new URL(`/research/${paperId}`, request.url))
}

Everything works fine down to the console.log("test") statement. But the redirect wouldn’t work. The browser stays on /research instead of redirecting to /research/[paperId].

I even tried redirecting from the client upon a successful response from the API, using useRouter and router.push() but even that wouldn’t work:

const handleFileDrop = async (file: File) => {
    // Convert file to blob
    const arrayBuffer = await file.arrayBuffer()
    const blob = new Blob([arrayBuffer])
    const paperId = await generateFileId(arrayBuffer)
    const paperTitle = file.name

    // Send file to api/upload for upload to S3
    try {
      const res = await fetch("/api/upload", {
        method: "POST",
        body: file,
      })
      if (!res.ok) throw new Error(await res.text())
      console.log(await res.json())
      const router = useRouter()
      router.push("/research/${paperId}")
    } catch (error: any) {
      // handle error
    }
  }

The browser remains on the originating URL.

I have two questions:

What is missing in my code, and What is the conventional approach to this situation? I need the new page to be able to access the file object too.


Solution

  • You cannot redirect a network call made via JavaScript like you did. Instead, you would return a normal response:

    import { NextResponse } from "next/server";
    
    export async function POST(request: NextRequest) {
      const data = await request.body;
      if (!data) return NextResponse.json({ success: false });
      const arrayBuffer = await streamToArrayBuffer(data);
      const blob = new Blob([arrayBuffer]);
      const loader = new PDFLoader(blob);
      const pageLevelDocs = await loader.load();
      const pageCount = pageLevelDocs.length;
      const paperId = await generateFileId(arrayBuffer);
    
      /*
      STEP 1: Upload file to S3
      STEP 2: Add reference to file in Postgres
      */
      console.log("test");
      return NextResponse.json({
        success: true,
        paperId: paperId,
      });
    }
    

    And handle the redirection on the client:

    // In the component body:
    const router = useRouter();
    
    const handleFileDrop = async (file: File) => {
      // Convert file to blob
      const arrayBuffer = await file.arrayBuffer();
      const blob = new Blob([arrayBuffer]);
      const paperId = await generateFileId(arrayBuffer);
      const paperTitle = file.name;
    
      // Send file to api/upload for upload to S3
      try {
        const res = await fetch("/api/upload", {
          method: "POST",
          body: file,
        });
        if (!res.ok) throw new Error(await res.text());
        const { paperId } = await res.json();
    
        router.push(`/research/${paperId}`);
      } catch (error: any) {
        // handle error
      }
    };
    

    At this point, if you have a research/[paperId]/page.tsx, you are good to go.