Dynamic URL malfunction

Hello, I am encountering a problem which I do not know where it comes from, I am a developer and in my code there is no data recovery problem, everything works fine, but the problem is that when I ‘arrives on a dynamic id page, that is to say that in my code I retrieve the list of my data and I assign to the button in question an href with the url included in the retrieved data, the field ’ Slug’ for my case in my data model, but no matter which button I press I find myself on the same page, even if the url is modified and never has the same, but the page is always linked to the last addition made on Builder.io, did you know how to solve this, I will put you a link with the site in question, the problem is on the “Work” page, and when you click on a work always the same spring but not with the same url.

https://kibo-website.vercel.app

Thank you for your help

Hello @AkiHiro,

Welcome to the Builder.io forum post.

We have tested the links and it seems that most of them have an undefined href value. Could you please share the steps or code you used to create those links? Thank you.

Hey @manish-sharma thanks for your time, here the part of the code about Builder.io

  builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!);

  const [works, setWork] = useState<WorkType[]>([]);

  useEffect(() => {
    const fetchy = async () => {
      try {
        const contents = (await builder.getAll('work')).map(
          (content) => content.data
        );
        setWork(contents as WorkType[]);
      } catch (error) {
        console.error('Error retrieving works:', error);
      }
    };

    fetchy();
  }, []);

{filteredSlides.map((slide, index) => (
        <swiper-slide key={index}>
          <div className="relative">
            <Link href={`/work/${slide.slug}`}>
              <TiltCard index={index}>
                {slide.isVideo ? (
                  <video
                    autoPlay
                    muted
                    loop
                    className='md:w-full w-3/4 xl:h-[32rem] md:h-[25rem] h-[18rem] max-w-2xl min-w-[8rem] m-auto object-cover rounded-xl bg-cover bg-no-repeat bg-center"'
                  >
                    <source src={slide.mainContent} type="video/mp4" />
                  </video>
                ) : (
                  <div
                    style={{ backgroundImage: `url(${slide.mainContent})` }}
                    className="md:w-full w-3/4 xl:h-[32rem] md:h-[25rem] h-[18rem] max-w-2xl min-w-[8rem] m-auto object-cover rounded-xl bg-cover bg-no-repeat bg-center"
                  />
                )}
              </TiltCard>
            </Link>
            <a
              className="absolute -bottom-5 left-1/2 -translate-x-1/2 font-semibold bg-white rounded-xl px-8 py-2"
              href={`/work/${slide.slug}`}
            >
              More
            </a>
          </div>
          {filteredWorks[index] && (
            <WorkDescription
              workDescription={filteredWorks[index]}
              isCarrousel
            />
          )}
        </swiper-slide>
      ))}

And the second part, when after I click on a work, so in my NextJs architecture it looks like this :

Capture d’écran 2024-07-09 à 10.16.34

'use client';

import { useEffect, useState } from 'react';
import { builder } from '@builder.io/sdk';

// Components
import SelectedWork from '@/components/organisms/work/SelectedWork';
import Header from '@/components/organisms/Header';
import Footer from '@/components/organisms/Footer';

import '../../globals.css';

const WorkId = ({ params }: { params: { slug: string } }) => {
  const [work, setWork] = useState<WorkType | null>(null);

  builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!);

  useEffect(() => {
    const fetchWork = async () => {
      try {
        console.log('Fetching work with URL:', params.slug);
        const content = await builder
          .get('work', {
            userAttributes: {
              urlPath: '/' + params.slug,
            },
          })
          .toPromise();
        console.log('Fetched content:', content.data); // Debugging log
        if (content) {
          setWork(content.data);
        } else {
          console.error('No content found for URL:', params.slug);
        }
      } catch (error) {
        console.error('Erreur lors de la récupération du travail:', error);
      }
    };
    fetchWork();
  }, [params.slug]);

  const [selectedDate, setSelectedDate] = useState<string | null>(null);
  const [searchInput, setSearchInput] = useState<string | null>('');

  return (
    <main className="flex flex-col flex-grow items-center gap-8 lg:mt-0 mt-4 w-full h-full transition-fade-out px-10">
      <div className="w-full">
        <Header
          selectedDate={selectedDate}
          setSelectedDate={setSelectedDate}
          searchInput={searchInput}
          setSearchInput={setSearchInput}
        />
      </div>

      {work ? <SelectedWork work={work} /> : <div>Work not found</div>}

      <div className="w-full h-full flex justify-end">
        <Footer />
      </div>
    </main>
  );
};

export default WorkId;

Thanks for your help :pray:

Hello @AkiHiro,

To correctly pre-render pages with dynamic paths, ensure your getStaticPaths and getStaticProps are set up correctly.

Here’s an example of how you can ensure this functionality works as expected:

// pages/work/[slug].tsx
import { builder } from '@builder.io/react';
import { useRouter } from 'next/router';
import SelectedWork from '@/components/organisms/work/SelectedWork';
import Header from '@/components/organisms/Header';
import Footer from '@/components/organisms/Footer';

// Initialize your Builder.io
builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!);

export async function getStaticPaths() {
  const works = await builder.getAll('work', {
    options: { noTargeting: true }
  });
  
  const paths = works.map((work) => ({
    params: { slug: work.data.slug }
  }));

  return {
    paths,
    fallback: 'blocking'
  };
}

export async function getStaticProps({ params }) {
  const content = await builder.get('work', {
    userAttributes: {
      urlPath: '/' + params.slug,
    },
  }).promise();

  return {
    props: {
      work: content?.data || null,
      notFound: !content
    },
    revalidate: 5
  };
}

const WorkId = ({ work }) => {
  const router = useRouter();
  const isPreviewing = useIsPreviewing();

  if (router.isFallback) {
    return <h1>Loading...</h1>;
  }

  if (!work && !isPreviewing) {
    return <div>Work not found</div>;
  }

  return (
    <main className="flex flex-col flex-grow items-center gap-8 lg:mt-0 mt-4 w-full h-full transition-fade-out px-10">
      <div className="w-full">
        <Header />
      </div>
      <SelectedWork work={work} />
      <div className="w-full h-full flex justify-end">
        <Footer />
      </div>
    </main>
  );
};

export default WorkId;
  • Log the params.slug before making the network request to ensure it’s correct.
  • Ensure that the returned content is not undefined.
  • Compare slug values to ensure there’s no mismatch.

Your integration seems almost correct, but the above additions and checks should help you pin down where the issue might lie.

Thanks,

Hey @manish-sharma , I adapted your code to mine, because I use AppRouter and not PageRouter about NextJs, so here’s mine :

'use client';

import { useIsPreviewing } from '@builder.io/react';
import { builder } from '@builder.io/sdk';
import { useEffect, useState } from 'react';

// Components
import SelectedWork from '@/components/organisms/work/SelectedWork';
import Header from '@/components/organisms/Header';
import Footer from '@/components/organisms/Footer';

import '../../globals.css';

builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!);

const fetchData = async (slug: string) => {
  const content = await builder
    .get('work', {
      userAttributes: {
        urlPath: '/' + slug,
      },
    })
    .promise();

  return content?.data || null;
};

const WorkId = ({ params }: { params: { slug: string } }) => {
  const [work, setWork] = useState<any>(null);
  const [selectedDate, setSelectedDate] = useState<string | null>(null);
  const [searchInput, setSearchInput] = useState<string | null>('');

  const isPreviewing = useIsPreviewing();
  console.log('first log', params.slug);

  useEffect(() => {
    console.log('second log', params.slug);

    const fetchWork = async () => {
      const content = await fetchData(params.slug);
      console.log('content', content);
      setWork(content);
    };

    fetchWork();
    console.log('third log', params.slug);
  }, [params.slug]);

  return !work && !isPreviewing ? (
    <div>Work not found</div>
  ) : (
    <main className="flex flex-col flex-grow items-center gap-8 lg:mt-0 mt-4 w-full h-full transition-fade-out px-10">
      <div className="w-full">
        <Header
          selectedDate={selectedDate}
          setSelectedDate={setSelectedDate}
          searchInput={searchInput}
          setSearchInput={setSearchInput}
        />
      </div>

      {work ? <SelectedWork work={work} /> : <div>Loading...</div>}

      <div className="w-full h-full flex justify-end">
        <Footer />
      </div>
    </main>
  );
};

export default WorkId;

and after that you can see the log of the slug, it was the good slug but not the good content, it’s really strange, I see “Work not found” like 0.2ms and after useEffect is trigger I see the content but the wrong content, everytime

Hello @AkiHiro,

The issue you’re encountering may be related to how the state is managed when you navigate between dynamic pages.

Ensure that the dynamic route components in Next.js properly re-render when the URL changes. You might need to leverage the key prop to force React to unmount and remount the component:

const WorkId = ({ params }: { params: { slug: string } }) => {
  const [work, setWork] = useState<any>(null);
  const [selectedDate, setSelectedDate] = useState<string | null>(null);
  const [searchInput, setSearchInput] = useState<string | null>('');

  const isPreviewing = useIsPreviewing();
  console.log('first log', params.slug);

  useEffect(() => {
    console.log('second log', params.slug);

    const fetchWork = async () => {
      const content = await fetchData(params.slug);
      console.log('content', content);
      setWork(content);
    };

    fetchWork();
    console.log('third log', params.slug);
  }, [params.slug]);

  return !work && !isPreviewing ? (
    <div>Work not found</div>
  ) : (
    <main className="flex flex-col flex-grow items-center gap-8 lg:mt-0 mt-4 w-full h-full transition-fade-out px-10" key={params.slug}>
      <div className="w-full">
        <Header
          selectedDate={selectedDate}
          setSelectedDate={setSelectedDate}
          searchInput={searchInput}
          setSearchInput={setSearchInput}
        />
      </div>

      {work ? <SelectedWork work={work} /> : <div>Loading...</div>}

      <div className="w-full h-full flex justify-end">
        <Footer />
      </div>
    </main>
  );
};

export default WorkId;

Confirm that the fetchData function is correctly handling the data retrieval based on the slug. It might help to log the urlPath in your fetchData function to ensure the correct path is being used:

const fetchData = async (slug: string) => {
  console.log('Fetching data for slug:', slug);  // Add this log
  const content = await builder
    .get('work', {
      userAttributes: {
        urlPath: '/' + slug,
      },
    })
    .promise();

  return content?.data || null;
};

Hello @manish-sharma, I try this and I added with your log :

'use client';

import { useIsPreviewing } from '@builder.io/react';
import { builder } from '@builder.io/sdk';
import { useEffect, useState } from 'react';

// Components
import SelectedWork from '@/components/organisms/work/SelectedWork';
import Header from '@/components/organisms/Header';
import Footer from '@/components/organisms/Footer';

import '../../globals.css';

builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!);

const fetchData = async (slug: string) => {
  console.log('Fetching data for slug:', slug);
  const content = await builder
    .get('work', {
      userAttributes: {
        urlPath: '/' + slug,
      },
    })
    .promise();

  return content?.data || null;
};

const WorkId = ({ params }: { params: { slug: string } }) => {
  const [work, setWork] = useState<any>(null);
  const [selectedDate, setSelectedDate] = useState<string | null>(null);
  const [searchInput, setSearchInput] = useState<string | null>('');

  const isPreviewing = useIsPreviewing();

  useEffect(() => {
    const fetchWork = async () => {
      const content = await fetchData(params.slug);
      console.log('content', content);
      setWork(content);
    };

    fetchWork();
  }, [params.slug]);

  return (
    <main
      key={params.slug}
      className="flex flex-col flex-grow items-center gap-8 lg:mt-0 mt-4 w-full h-full transition-fade-out px-10"
    >
      {!work && !isPreviewing ? (
        <div>Work not found</div>
      ) : (
        <>
          <div className="w-full">
            <Header
              selectedDate={selectedDate}
              setSelectedDate={setSelectedDate}
              searchInput={searchInput}
              setSearchInput={setSearchInput}
            />
          </div>

          {work ? <SelectedWork work={work} /> : <div>Loading...</div>}

          <div className="w-full h-full flex justify-end">
            <Footer />
          </div>
        </>
      )}
    </main>
  );
};

export default WorkId;

But the log returns the correct path but still the wrong content, I try to add a key to my to reload it when params.slug changes, but nothing, I’m starting to wonder if there is a bug Builder side haha

Hello @AkiHiro,

There seems to be an issue with the implementation, could you try updating the code as shown below and let us know how that works for you?

'use client';

import { useIsPreviewing, BuilderComponent } from '@builder.io/react';
import { builder } from '@builder.io/sdk';
import { useEffect, useState } from 'react';

// Components
import SelectedWork from '@/components/organisms/work/SelectedWork';
import Header from '@/components/organisms/Header';
import Footer from '@/components/organisms/Footer';

import '../../globals.css';

builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!);

const fetchData = async (slug: string) => {
  console.log('Fetching data for slug:', slug);
  const content = await builder
    .get('work', {
      userAttributes: {
        urlPath: '/' + slug,
      },
    })
    .promise();

  return content?.data || null;
};

const WorkId = ({ params }: { params: { slug: string } }) => {
  const [work, setWork] = useState<any>(null);
  const [selectedDate, setSelectedDate] = useState<string | null>(null);
  const [searchInput, setSearchInput] = useState<string | null>('');

  const isPreviewing = useIsPreviewing();

  useEffect(() => {
    const fetchWork = async () => {
      const content = await fetchData(params.slug);
      console.log('content', content);
      setWork(content);
    };

    fetchWork();
  }, [params.slug]);

  return (
    <main
      key={params.slug}
      className="flex flex-col flex-grow items-center gap-8 lg:mt-0 mt-4 w-full h-full transition-fade-out px-10"
    >
      {!work && !isPreviewing ? (
        <div>Work not found</div>
      ) : (
        <>
          <div className="w-full">
            <Header
              selectedDate={selectedDate}
              setSelectedDate={setSelectedDate}
              searchInput={searchInput}
              setSearchInput={setSearchInput}
            />
          </div>

          {work ? <SelectedWork work={work} /> : <div>Loading...</div>}

          <div className="w-full h-full flex justify-end">
            <Footer />
          </div>
          
          {/* Render BuilderComponent for the visual editor */}
          {isPreviewing && (
            <BuilderComponent model="work" content={work} />
          )}
        </>
      )}
    </main>
  );
};

export default WorkId;