This question already has an answer here:
How do I handle phone numbers with NextJS Links?
(1 answer)
Closed 10 months ago.
Currently I am using nextjs/Link for handling tlf number and email inside of a contacts section in my footer. I use href property to send the user to mailto or directly to google maps when they click on the respective link. I read that using next/link for this purpose is not a good practise. What should i be using instead?
here is a snippet of my code:
interface Props {
contact: ContactType;
}
function Contact({ contact }: Props) {
return (
<Link href={contact.href ?? ''}>
<a className="flex gap-x-5" target={contact.target ?? ''}>
{contact.icon ? (
<div className="relative w-5 h-5">
<Image width={15} height={15} src={contact.icon} />
</div>
) : null}
<p> {contact.name} </p>
</a>
</Link>
);
}
export default Contact;
If your links lead to an external resource then you can use regular <a> tag without Next.js <Link>, same with mail or phone links and etc. Basically Link is only needed if you want to have SPA internal navigation between your own routes (navigation that will not reload the page).
Related
I am trying to implement Cookiebot on my NextJs Website.
The docs (Link to docs) mention to insert the script for the cookie declaration inside the HTML of the specific page, for me it is an own subpage only for the cookie declaration.
If I use the HTML element <script ... /> as mentioned in the docs, the text does not load when I switch to the page, but only after a refresh once I'm on the page.
If I use the NextJs Script component Link to docs by switching the <script ..> to and import it from next/script, the cookie declaration loads instantly as expected, but it is placed below everything else of the page, even under the footer (even though i wrap the pages inside a layout with footer).
My code for the layout looks like following:
`
function BasePage(props: IBasePageProps) {
return (
<div>
<Navbar />
<main>{props.children}</main>
<Footer />
</div>
);
}
This works for every page and everything - the pages are all between the Navbar and Footer. However if I use following code for the page of the screenshot, the <Script .../> text is loaded below the footer.
export default function CookieRichtlinie() {
return (
<>
<h1 className="pb-8 mt-2 text-3xl font-extrabold leading-8 tracking-tight text-gray-900 sm:text-4xl">
Cookie Richtlinie
</h1>
<div id="CookiePolicyText">
<Script
id="CookieDeclaration"
src="https://consent.cookiebot.com/12345/cd.js"
type="text/javascript"
async
/>
</div>
</>
);
`
After googling for two days I couldn't find any issue or thread close to this problem. Someone experienced this or know what I could try?
<Script ...> below everything else
Placed <Script ...> tag of next/script anywhere possible to get the loaded text inside the page. Expected to have the result of the script in the placed component, however it is always at the bottom of the page.
Did some global searches in Github and found an useEffect workaround, made some modifications and this worked in my case.
useEffect(() => {
const cookieBotWrapper = document.getElementById("CookiebotDeclaration")
if (cookieBotWrapper) {
const script = document.createElement("script")
script.id = "CookieDeclaration"
script.type = "text/javascript"
script.async = true
script.src = `https://consent.cookiebot.com/${process.env.NEXT_PUBLIC_COOKIEBOT_DOMAIN_GROUP_ID}/cd.js`
cookieBotWrapper.appendChild(script)
}
}, [])
return (
<main>
{process.env.NODE_ENV !== "development" && (
<div id="CookiebotDeclaration" />
)}
</main>
)
I have a page where I fetch data and map through it.
In my map function I display a card component with some data like this:
pokemonsList?.map((pokemon, index) => {
return (
<Link href={`/pokemon/${pokemon.id}`} key={index}>
<a>
<Card pokemon={pokemon} />
</a>
</Link>
);
}
As you can see, the route is dynamic.
What I would like to do is to pass the whole pokemon object to the page.
I would like to achieve this without using the next router query method, because the object contains a lot of data.
Is there an other way ?
You could cache it, either by using some global state management package (Redux, React Query) or inbuilt Context API.
Or
<Link
href={{
pathname: '/pokemon',
query: {
id: pokemon.id,
pokemon: JSON.stringify(pokemon)
}
}}
as={`/pokemon/${pokemon.id}`}
key={index}>
<a>
<Card pokemon={pokemon} />
</a>
</Link>
And then on the page
const { query } = useRouter();
const pokemon = JSON.parse(query.pokemon);
From my understanding NEXT should be automatically generating the routes based on my folder structure.
I am mapping over article posts on news/index.tsx page but the urls I get are localhost3000/article-one when I need localhost3000/news/article-one
Can anyone point out where I'm going wrong?
{page?.articles.map((post, i) => {
return (
<Link
key={i}
href={post?.slug.current!}
>
{post?.title!}
</Link>
)
})}
Folder structure:
- pages
- news
- index.tsx
- [slug].tsx
EDIT
Addiction info:
Slugs are being pulled from Sanity headless CMS.
Tutorials often show routing by prepending news/ to the slug but this in turn is prepending news/ to all slugs
You can handle the dynamic routes in Next js in few ways, one of those is with With URL Object:
{page?.articles.map((post, i) => {
return (
<Link
key={i}
href={{
pathname: '/news/[slug]',
query: { slug: post?.slug?.current },
}}
>
<a>
{post?.title!}
</a>
</Link>
)
})}
Additional disclaimer. You always must add the anchor <a>...</a> as a child of Link Component.
Documentation: https://nextjs.org/docs/api-reference/next/link#with-url-object
Turns out to be a really simple fix..
The nav links that were getting /news/news prepended to their slugs needed a / prepended before them
From what I understand you should change your code to have the correct href:
{page?.articles.map((post, i) => {
return (
<Link
key={i}
href={`/news${post?.slug.current!}`}
>
{post?.title!}
</Link>
)
})}
When I click on Hello, it redirects ok but to browse back to where i was by clicking back button, it requires 2 back button clicks. (probably because <Link> and <a> are triggered at the same time)
export default function Navigation() {
const router = useRouter()
const menus = [
{ key: 'key1', title: 'title1', clasName: 'class1' },
{ key: 'key2', title: 'title2', clasName: 'class2' },
]
return (
<div role="tablist">
{menus.map(({ key, title, className }) => (
<Link
href={{
pathname: router.pathname,
query: {
menu: key
}
}}>
<a
className={className}
role="tab">
<span>{title}</span>
</a>
</Link>
))}
</div>
)
}
I have to use <a> to apply className.
Only using <a> causes page to rerender even when href is same as current page.
What should I do to prevent 2 history stacks being inserted?
Thanks!
Apparently it was my custom popstate event listener that was pushing an extra stack into History. Thanks all for your comments
I am working with nextjs with next-auth for google oauth. I made a custom signin page keeping the component which would launch the google oauth page within a form
/auth/signin.jsx
const signIn = ({providers}) => {
return (
...
<form className="flex flex-col space-y-1 mt-6">
...
{Object.values(providers).map((provider) =>(
<div key={provider.name}>
<button className="p-3 bg-blue-500 rounded-lg text-white"
onClick={() => SignIntoProviders(provider.id)}
>
Login with {provider.name}
</button>
</div>
))}
</form>
...
)
}
providers is an object I get using getProviders() from next-auth
The google oauth works when onClick={() => SignIntoProviders(provider.id)} is placed outside the form. However when kept within the form, I get "http://localhost:3000/auth/signin?". Any idea why?
Late to the game (and hopefully this is no longer an issue for you), but I'd guess that this is a case of the form submit (and subsequent POST) interfering with the onClick handler you've registered. The first button within a form gets treated as a submit type, unless explicitly set otherwise. You could set the button to explicitly not be a submit button (e.g. <button type='button'>), but that will leave the other form submission behavior (e.g. submitting when the user hits Enter) in place, which could result in a user getting that same error screen.
Instead, overriding the form submission handler can ensure you're only performing the requests you intend to. If you don't want to allow the default form submission behavior, you could override the default onSubmit event for the form to something like:
<form
onSubmit={(e) => { e.preventDefault(); return false; }
className="flex flex-col space-y-1 mt-6"
>
If you do want to keep the default form submission behavior, you could also remove the click handler from your button directly, and instead add SignIntoProviders(provider.id) into the onSubmit listener from above, e.g.
<form
onSubmit={(e) => {
SignIntoProviders(provider.id);
e.preventDefault();
return false;
}
className="flex flex-col space-y-1 mt-6"
>