How to navigate to subpages? - next.js

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>
)
})}

Related

NextJs Script not rendering inside component

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>
)

How to pass a big data object to another page with dynamic route in next js (without query params)?

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);

react-helmet changing css in the head tag during runtime has lag (no css for 1 sec before it shows updated css)

This is a simplified React component that uses helmet to update the link css on runtime:
function App() {
const [brand, setBrand] = useState('nike')
return (
<div className="App">
<Helmet>
<link rel="stylesheet" href={getBrandStyle(brand)} />
</Helmet>
<div>other contents here</div>
<!-- omitted the button components that change the brand state by calling setBrand -->
</div>
);
}
I have recently just used react-helmet as a declarative way to change the head tag's child and with the code I wrote above, when switching the css there is momentary lag when the page has no css stylings and then 1 second later the updated css shows up.
Even during the initial load of the page, if I use queryParameters (code above doesn't show the query parameter approach) such as
https://localhost:3000?brandQueryParam=nike
there is 1 second wherein there is no css styling before the brand css shows up.
Can you please let me know what I am missing and how to resolve this?
This is the solution that I came up with, not sure if setTimeout is the best solution so if anyone else knows a better way, please share it.
const brands = {
nike: 'nike2022',
adidas: 'adidas2017',
fila: 'fila2020'
};
function App() {
const [brand, setBrand] = useState('nike')
const [isLoading, setIsLoading] = useState(false)
const changeBrandStyleOnClick = (brand) => {
setBrand(brand)
setIsLoading(true)
}
return (
<div className="App">
<Helmet>
<link rel="stylesheet"
onChangeClientState={(newState, addedTags, removedTags) => setTimeout(() => setIsLoading(false), 1500)}
href={getBrandStyle(brand)} />
</Helmet>
{isLoading && (
<Overlay>
<Spinner/>
</Overlay>
)}
{!isLoading && (
<>
{Object.keys(brands).filter(b => b !== brand).map(b =>
(<Button onClick={() => changeBrandStyleOnClick (b)} value={b}>
<Logo
alt="default alt name"
appearance="default"
name={b}
size="small"/>
</Button>))
}
<div>other contents here</div>
</>
)}
</div>
);
}

nextjs <a> inside <Link> results in 2 history stacks

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

Next.js pretty seo url passing query parameters

I have a product page with path product/[id]/index.js. I would like to 'prettify' the product url for SEO purposes. I would like the url to look like product/usb-mouse-black instead of product/121122-001 I need to pass the query ID to populate the product details.
slug=usb-mouse-black
<Link as={`/reservas/${slug}`} href={{pathname: `/product/[id]`, query: {id: idkey}}}>
<Button disableElevation size="small" className={classes.button} variant="contained" color="primary" onClick={(event) => { return true }}>Solicitar Disponibilidax</Button>
</Link>
ID=121122-001
Then I try to get the ID :
const { id } = router.query
Currently getting usb-mouse-black as ID instead of 121122-001
It is better to parse the product name on product/[id]/index.js rather than specifying in the Link tag. It will improve the SSO effect in Google and other search engines.
const { slug } = router.query
const id = parseSlugToId(slug);
If you still prefer your way, try this:
<Link as={`/product/${slug}`} href={`/product/${idkey}`}>{ /* your code goes here */</Link>
I just use the Link in it's most simplified form:
<Link href={`/product/${idkey}`}>
<Button disableElevation size="small" className={classes.button} variant="contained" color="primary" onClick={(event) => { return true }}>Solicitar Disponibilidax</Button>
</Link>
Remember that /reservas/[id]/index.js is the same as /reservas/[id].js no further configuration is necessary

Resources