اذهب إلى المحتوى
  • 0

مشكل في رفع مشروع Nextjs 14 على vercel

محمود_سعداوي

السؤال

السلام عليكم.

عند رفع المشروع على vercel، ظهرت الأخطاء التالية:

  q [Error]: Dynamic server usage: Route /api/properties/search couldn't be rendered statically because it used `request.url`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error
    at W (/vercel/path0/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:21106)
    at Object.get (/vercel/path0/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:28459)
    at c (/vercel/path0/.next/server/app/api/properties/search/route.js:1:607)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async /vercel/path0/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:36258


description: "Route /api/properties/search couldn't be rendered statically because it used `request.url`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error",
  digest: 'DYNAMIC_SERVER_USAGE'

للتوضيح: هذه بعض المكونات التي أشير لي بأنها تحتوي أخطاء علما وأن المشروع يعمل بصفة عادية على المتصفح

api/properties

import cloudinary from "@/config/cloudinary";
import connectDB from "@/config/database";
import Property from "@/models/Property";
import { getSessionUser } from "@/utils/getSessionUser";

/**
 * method: GET
 * route  : /api/properties
 */
export const GET = async (request) => {
  try {
    await connectDB();

    const page = request.nextUrl.searchParams.get("page") || 1;
    const pageSize = request.nextUrl.searchParams.get("pageSize") || 5;
    const skip = (page - 1) * pageSize;

    const total = await Property.countDocuments({});
    const properties = await Property.find({}).skip(skip).limit(pageSize);

    const result = {
      total,
      properties,
    };

    return new Response(JSON.stringify(result), { status: 200 });
  } catch (error) {
    console.log(error);
    return new Response("Something went wrong", { status: 500 });
  }
};

/**
 * method: POST
 * route : api/properties/add
 */
export const POST = async (request) => {
  try {
    await connectDB();

    const sessionUser = await getSessionUser();
    if (!sessionUser || !sessionUser.userId) {
      return new Response("UserId is required", {
        status: 401,
      });
    }
    const { userId } = sessionUser;

    const formData = await request.formData();

    const amenities = formData.getAll("amenities");
    const images = formData.getAll("images").filter((img) => img.name !== "");
    const propertyData = {
      type: formData.get("type"),
      name: formData.get("name"),
      description: formData.get("description"),
      location: {
        street: formData.get("location.street"),
        city: formData.get("location.city"),
        state: formData.get("location.state"),
        zipcode: formData.get("location.zipcode"),
      },
      beds: formData.get("beds"),
      baths: formData.get("baths"),
      square_feet: formData.get("square_feet"),
      amenities,
      rates: {
        weekly: formData.get("rates.weekly"),
        monthly: formData.get("rates.monthly"),
        nightly: formData.get("rates.nightly"),
      },
      seller_info: {
        name: formData.get("seller_info.name"),
        email: formData.get("seller_info.email"),
        phone: formData.get("seller_info.phone"),
      },
      owner: userId,
    };

    // Upload Images To Cloudinary
    const imageUploadPromises = [];

    for (const image of images) {
      const imageBuffer = await image.arrayBuffer();
      const imageArray = Array.from(new Uint8Array(imageBuffer));
      const imageData = Buffer.from(imageArray);

      // Convert The Image Data To Base64
      const imageBase64 = imageData.toString("base64");

      // Make request to upload to cloudinary
      const result = await cloudinary.uploader.upload(
        `data:image/png;base64,${imageBase64}`,
        {
          folder: "propertypulse",
          secure: true,
          rejectUnauthorized: false,
        }
      );
      imageUploadPromises.push(result.secure_url);

      // Wait for all images to upload
      const uploadedImages = await Promise.all(imageUploadPromises);
      // Add Uploaded images to propertyData object
      propertyData.images = uploadedImages;
    }

    const newProperty = new Property(propertyData);
    await newProperty.save();

    return Response.redirect(
      `${process.env.NEXTAUTH_URL}/properties/${newProperty._id}`
    );

    // return new Response(JSON.stringify({message: 'success'}), { status: 200 })
  } catch (error) {
    console.error("This is the error we are looking for: ", error);
    return new Response("Failed to add property", { status: 500 });
  }
};

/api/properties/search

import connectDB from "@/config/database";
import Property from "@/models/Property";

/**
 * method: GET
 * route : /api/properties/search
 */
export const GET = async (request) => {
  try {
    await connectDB();

    const { searchParams } = new URL(request.url);
    const location = searchParams.get("location");
    const propertyType = searchParams.get("propertyType");

    const locationPattern = new RegExp(location, "i");

    // Match location pattern against database fields
    let query = {
      $or: [
        { name: locationPattern },
        { description: locationPattern },
        { "location.street": locationPattern },
        { "location.city": locationPattern },
        { "location.state": locationPattern },
        { "location.zipcode": locationPattern },
      ],
    };

    // Only check for property if its not 'All'
    if (propertyType && propertyType !== 'All') {
        const typePattern = new RegExp(propertyType, "i");
        query.type = typePattern
    }

    const properties = await Property.find(query)

    return new Response(JSON.stringify(properties), { status: 200 });
  } catch (error) {
    console.log(error);
    return new Response("Something went wrong => Search Properties", {
      status: 500,
    });
  }
};

package.json

{
  "name": "next_app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@maptiler/leaflet-maptilersdk": "^2.0.0",
    "cloudinary": "^2.2.0",
    "leaflet": "^1.9.4",
    "leaflet-control-geocoder": "^2.4.0",
    "leaflet-defaulticon-compatibility": "^0.1.2",
    "mongodb": "^6.8.0",
    "mongoose": "^8.4.4",
    "next": "14.2.4",
    "next-auth": "^4.24.7",
    "opencage-api-client": "^1.0.7",
    "react": "^18",
    "react-dom": "^18",
    "react-icons": "^5.2.1",
    "react-leaflet": "^4.2.1",
    "react-photoswipe-gallery": "^1.3.9",
    "react-share": "^5.1.0",
    "react-spinners": "^0.14.1",
    "react-toastify": "^10.0.5"
  },
  "devDependencies": {
    "postcss": "^8",
    "tailwindcss": "^3.4.1"
  }
}

شكرا لكم.

تم التعديل في بواسطة محمود سعداوي2
رابط هذا التعليق
شارك على الشبكات الإجتماعية

Recommended Posts

  • 0

رسالة الخطأ معناها إن Next.js غير قادر على توليد صفحة ثابتة static render للمسار /api/properties/search وذلك لأنه يستخدم request.url وهو قيمة ديناميكية لا يمكن تحديدها وقت بناء المشروع.

مسارات الـ API يتم تشغيلها على السيرفر عند طلبها بشكل افتراضي server-side rendered ولكن عندما تنشر التطبيق على Vercel، يحاول Next.js توليد مسارات الـ API بشكل ثابت وقت البناء، وهو أمر غير ممكن بسبب استخدام request.url.

قم  باستخدام دالة getStaticProps لتوليد المسار بشكل ثابت وقت البناء وستحتاج لتوفير قيمة افتراضية لـ request.url لأنها غير متاحة وقت البناء، وبإمكانك محاولة استخدام خيار revalidate في getStaticProps لإعادة توليد المسار عند كل طلب، فذلك يسمح لـ Next.js بإعادة توليد المسار بشكل ديناميكي عند كل طلب.

رابط هذا التعليق
شارك على الشبكات الإجتماعية

  • 0
بتاريخ 46 دقائق مضت قال Mustafa Suleiman:

قم  باستخدام دالة getStaticProps لتوليد المسار بشكل ثابت وقت البناء

مرحبا مجددا @Mustafa Suleiman

المشكل أني أستعمل nextjs 14 حيث أستخدم useEffect لجلب البيانات.

هل تيعين علي تغيير الكود.

كمثال:

const pageSearchResultsPage = () => {
  const [properties, setProperties] = useState([]);
  const [loading, setLoading] = useState(true);

  const searchParams = useSearchParams();
  const location = searchParams.get("location");
  const propertyType = searchParams.get("propertyType");

  useEffect(() => {
    const fetchSearchResults = async () => {
      try {
        const res = await fetch(
          `/api/properties/search?location=${location}&propertyType=${propertyType}`
        );
        if (res.status === 200) {
          const data = await res.json();
          console.log('data')
          setProperties(data);
        } else {
          setProperties([]);
        }
      } catch (error) {
        console.log(error);
        toast.error("Fetching search properties is failed!");
      } finally {
        setLoading(false);
      }
    };
    fetchSearchResults();
  }, [location, propertyType]);
  return (
    <>
      <section className="bg-green-700 py-4">
        <div className="max-w-7xl mx-auto px-4 flex flex-col items-start sm:px-6 lg:px-8">
          <PropertySearchForm />
        </div>
      </section>
      {loading ? (
        <Spinner loading={loading} />
      ) : (
        <section className="px-4 py-6">
          <div className="container-xl lg:container m-auto px-4 py-6">
            <Link
              href="/properties"
              className="flex items-center text-green-500 hover:underline mb-3"
            >
              <FaArrowAltCircleLeft className="mr-2 mb-1" /> Back To Properties
            </Link>
            <h1 className="text-2xl mb-4">Search Results</h1>
            {properties.length === 0 ? (
              <p>No search results found</p>
            ) : (
              <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
                {properties.map((property) => (
                  <PropertyCard key={property._id} property={property} />
                ))}
              </div>
            )}
          </div>
        </section>
      )}
    </>
  );
};

شكرا

رابط هذا التعليق
شارك على الشبكات الإجتماعية

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...