I'm migrating a website developed with Gatsby to NextJS, but something I could achieve using Gatsby's createPage API is localizing the app's routes, but until now I couldn't achieve this with NextJS APIs.
I'm using Next v10.0.1 for this.
As I see in other threads regarding this type of resource, this is actually kinda confusing of what it actually means, so here goes an example of what is the desired result:
User access route /my-data/1234 (where the NextJS equivalent routing would be: /my-data/[dataId].js)
User must be able to access the same page but translated in the URL /pt/meus-dados/1234 (using, for example, portuguese translation).
Some guesses on how to achieve that keeping Next's static optimizations (Static rendering and Incrementing Static Rendering)?
I actually found an answer which is pretty useful for my use case, I'm using NextJS rewrites for that, maybe not the best solution, but fits my needs well.
I use a single file for each route, so the directory structure should be something like this:
pages
-- my-data
-- [id].js
then I'll have some kind of internationalization, in my case I'm using react-i18next, won't think about the aspects of implementations for the library here, it could also be achieved with any other.
Next step is to set a translation somewhere for the pages routes, for example, add an entry for the i18next messages named routes containing a key-value pair for the routes translations. e.g (intl/pt.json):
{
...
"routes": {
"/my-data/:id": "/meus-dados/:id"
}
...
}
and then use NextJS rewrites, so you will have to import the intl messages (e.g: from intl/pt.json) and map them as rewrites in next.config.js:
# next.config.js
...
async rewrites() {
// languages here is a key-value pair object containing the structure { [language]: { routes: {...} } }
// in my case, imported from `intl/pt.json`
const intlRewrites = Object.entries(languages).reduce((rewrites, [ language, { routes } ]) => {
return [
...rewrites,
...Object.entries(pages).map(([ path, translatedPath ]) => {
const source = translatedPath
const destination = path // here you can write a logic to add a language prefix if needed
return { source, destination }
})
]
}, [])
return intlRewrites
}
From my experience, optimizations like ISG work fine. Should work with SSG too, but I haven't tested it.
I've been tackling this exact problem, and whilst I don't yet have a solution that integrates directly into NextJS, this can be achieved fairly simply before your project is compiled.
If you were to organise your pages directory as follows, it should work as you expect:
// Before
pages
-- my-data
-- [id].js
// After
pages
-- pt
-- meus-dados
-- [id].js
-- en
-- my-data
-- [id].js
However the developer experience here isn't nice. So what I have done to solve this currently is written a simple build step that runs before next build. It takes a regular pages directory and converts it to the above format, allowing next build to run against a version that works as intended for the translated paths. This allows SSG and ISG to work as expected.
Ideally I'd like to hook into the Next ecosystem so this works seamlessly for dev and build, but I haven't yet gotten that far
Related
We are about to build a new react based frontend layer for a large and complex news website. We are considering using nextJS, but one thing that puzzles me is the built-in file based routing in nextJS. In our website, content always belong to a section and sections are represented by the path in the URL.
Example:
/regions/region-A
/regions/region-B
…
/area-of-expertise/tax
/area-of-expertise/macro-economics
…
The sections (regions, region-A, region-B, area-of-expertise, tax and macro-economics) are created in our CMS and there could be various type of content in every section.
The “tax”-section (that is a subsection of “area-of-expertise”) could contain different types of content, e.g., regular news articles and reports. The visual representation of a news article and a report is different, but there is no way to tell the difference between them by just looking at the URL. Example:
/area-of-expertise/tax/title-of-news-article_id.html
/area-of-expertise/tax/title-of-report_id.html
Considering the above, would you still consider nextJS to be a good alternative for us? Would you recommend using another routing library?
I don't know if NextJS is the best framework for your needs, but you may want to look into per-page layouts, which would allow each individual page to optionally specify it's layout.
You'd define a NewsArticleLayout and a ReportLayout, then have your components for each route define a getLayout function.
For example, /area-of-expertise/tax/title-of-report_id.html could look something like this:
import Layout from '../../../components/layouts/Layout';
import ReportLayout from '../../../components/layouts/ReportLayout';
export default function Page() {
return (
/** Report content goes here */
)
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<ReportLayout>{page}</ReportLayout>
</Layout>
)
}
I've tried to check through the official documentation, various issues, and inside SO previous questions, but I can't find confirmation if it's possible to create a dynamic route in Next.js that contains a constant or a string combined with a slug. For example?
pages/
something-[slug].jsx
Is it possible or not? I'm inclined to think not because of the examples I've tried to build, but possibly I'm missing something.
While Next.js doesn't provide built-in support for partial dynamic routes (like something-[slug]), you can work around it by setting up an actual dynamic route and use rewrites to map the incoming URL (in the format you want) to that route.
For instance, you could setup a dynamic route under /pages/something/[slug].jsx, then configure a rewrites rule in next.config.js as follows.
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/something-:slug',
destination: '/something/:slug'
}
];
}
}
In the latest few release of NextJS, the router has become case sensitive. I've been slowly re-writing our conference web site with NextJS and I've noticed that much of our valued SEO is stored by google and others as cased and will cause 404's.
For example, this URL is discoverable by googling "Douglas Crockford Silicon Valley Code Camp".
https://www.siliconvalley-codecamp.com/Session/2018/qa-with-douglas-crockford
Is there a way with NextJS, when running in production to somehow lowercase all incoming URL's, even as some kind of redirect?
I'm following the pattern they have here: https://nextjs.org/docs/routing/dynamic-routes and my app is using GetStaticPaths, as I plan to use ISR (incremental static regen) so it needs to work with that also.
Also, since the site has always been case insensitive, URL's are stored in various ways so I can't just make it to what Google stores it as.
I will assume that you have something like that: /articles/:slug. I would get the slug using the router and then create a hook at mount time to redirect to "/articles/:lowercaseSlug" if slug has any capital letters.
import { useRouter } from 'next/router'
const router = useRouter;
const { slug } = router.query;
useEffect(() => {
if (slug.toLowerCase() !== slug) // Check if it has any capital letters
router.push(`/articles/${slug.toLowerCase()}`) // Redirect to the working path
}, [])
We have a large and complex traditional React app that we've been building for the last couple of years. It loads an index.html injects javascript and gets data from an API as is usual. Unfortunately, cold load times are pretty bad (5 - 7 seconds on average). Once everything loads, it's snappy as usual but the cold load times are killing us in specific "critical" pages. These are our public user pages, in the format of: https://mywebsite/userId
We're looking for a way to dramatically speed up loading times for these routes, with methods that go beyond code-splitting or resource optimization. We already do those, and are serving our app off a CDN.
We've looked at creating static "snapshots" of these user pages, that we need to load very fast using something like react-static, and serving them as static versions and hydrating them later. Rewriting our project using something like next.js or gatsby is not an option as it would entail too much work. SSR is also not an option as our entire backend is coded in Django rather than Node.js
Are we on the right track? Is it possible / worth it to use react-static (or a similar tool) to do this? There is a LOT of documentation on how to create react-static projects from scratch but nothing on how to convert an existing project over, even if it's just a small subset of routes like we need.
Also, once the data changes on our user pages, how do we trigger a "rebuild" of the appropriate snapshot? Users don't update their data that often, about 3 of 4 times per month, but we have 3K users, so maybe 15 updates per hour would be the average. Can we trigger only a rebuild of the routes that actually changed?
Like you said, you could use react-static.
They have a feature which fills exactly with your need ( user's specific pages ).
In their example they use an array of posts to generate a specific static file for each of them.
This have a huge lesser amount of time taken to load, as it's only html static files.
Imagine having this scenario:
[
{
id: 'foo',
...
},
{
id: 'bar',
...
},
...
]
Following the example below this would generate something like this ( at runtime ):
- src
- pages
- blog
- posts
- foo // Specific post page
- bar // Specific post page
Look at into the example:
//static.config.js
export default {
// resolves an array of route objects
getRoutes: async () => {
// this is where you can make requests for data that will be needed for all
// routes or multiple routes - values returned can then be reused in route objects below
// ATTENTION: In here, instead of posts you'd fetch your users json data
const { data: posts } = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
return [
// route object
{
// React Static looks for files in src/pages (see plugins below) and matches them to path
path: "/blog",
// function that returns data for this specific route
getData: () => ({
posts
}),
// an array of children routes
// in this case we are mapping through the blog posts from the post variable above
// and setting a custom route for each one based off their post id
children: posts.map(post => ({
path: `/post/${post.id}`,
// location of template for child route
template: "src/containers/Post",
// passing the individual post data needed
getData: () => ({
post
})
}))
},
];
},
// basic template default plugins
plugins: [
[
require.resolve("react-static-plugin-source-filesystem"),
{
location: path.resolve("./src/pages")
}
],
require.resolve("react-static-plugin-reach-router"),
require.resolve("react-static-plugin-sitemap")
]
};
You can use a Service Worker.
Load the important fast pages as static, then in the background, using the Service worker load the longer resources.
You can also use a Service Worker for smart caching.
For example, the server can set a cookie with the current resource version (comes with that first page), and the Service worker can compare this to it’s resource version , and decide whether to loat it from cache or go to the server.
I carefully read the docs of next routing system.
It only mentions that I could achieve dynamic routing like this:
http://localhost:3000/level1/dynamicSlug
But I am trying to achive something like this:
http://localhost:3000/level1/level2/dynamicSlug
And I want level2 to be created dynamic too
Thanks so much !
It is possible to do nested scenarios according to your request in this way.
for example:
pages/
level1/
[dynamicSlug]/
- index.js // will match for /level1/1234
level2/
- index.js // will match for /level1/level2
- [dynamicSlug].js // will match for /level1/level2/1234
Or
pages/
level1/
[dynamicSlug]/
- index.js // will match for /level1/1234
level2/
- index.js // will match for /level1/level2
[dynamicSlug]/
- index.js // will match for /level1/level2/1234
You have 2 choices:
Using v9 Dynamic Routing by calling the folder as [dynSlag] and putting your page file inside.
Using custom server and routing, you will need to define a custom server, map your path to a specific next page.
I know this is a bit of an old post, but I'd just like to share my working response with NextJS v11.
I want dynamic routing at two levels. E.g.:
{siteroot}/dynamicPage
{siteroot}/dynamicUrlSection/dynamicPage
My folder structure is:
/pages/[section]/[page].tsx
/pages/[section]/index.tsx
This way, the "dynamicPage" path at the root is handled by index.tsx, and nested routes are handled by [page].tsx
BONUS INFO: I am working with Contentful as a CMS. I use a single content model for all the pages at both levels.
The model has "section" and "page" properties.
The entries that serve the root dynamic pages (i.e. /pages/[section]/index) have a compound value in the "page" property of {section}-index. I then have to be a bit smart in my client code:
if (!page) {
page = `${section}-index`;
}
await fetchData(section, page);
Using the example right from the NextJs documentation, I use this hack, maybe you could use it.
<Link href="/posts/[id]" as={`/posts/${subFolder}${id}`}>
"as" will have a value like /posts/nested_subfolder_file.md
And in the getPostData function, just do this little change:
const nestedPaths = id.split('_')
const fileName = `${nestedPaths.pop()}.md`
const fullPath = path.join(postsDirectory, ...nestedPaths, fileName)