Optional dynamic route without a custom server - NextJS - 9.5.2 - javascript

I am trying to create localized routes with optional first param like /lang?/../../, but without a custom server.
From 9.5 NextJS has this option dynamic optionall parameters, if you set the folder or a file with a name:
[[...param]]. I did that.
The problem is that, i have other routes and I want all of them to be with that lang prefix, ut optional with default language, if that lang is not provided
I have a folder [[...lang]] with a file index.js, with simple function component just for testing. Now optional parameter works for the home page / and /en, but I have other files, which I want to be with that optional lang. For the example, I have about.js and I want to access it via /en/about and /about.
I can't put about.js inside [[...lang]], because, I am getting an error:
Failed to reload dynamic routes: Error: Catch-all must be the last part of the URL.
I know what it says and why is that, but I have a fixed collection of languages ['en', 'fr'] and I can check is there a lang.
Is there a way, without a custom server to use optionally a dynamic first part of the path, like
/en/about and /about ?

I think you are talking about this feature. Have a look on this https://nextjs.org/blog/next-9-5#support-for-rewrites-redirects-and-headers

To extend the answer from #Vibhav, in next.config.js:
const nextConfig = {
async rewrites(){
return [
// URLs without a base route lang param like /my-page
{
source: '/',
destination: '/'
},
// URLs with a base route lang param like /en/my-page
{
source: '/:lang*/:page*',
destination: '/:page*'
},
// URLs `/en/post/post_id`
{
source: '/:lang/:path/:page',
destination: '/:path/:page'
},
]
}
};
module.exports = withBundleAnalyzer(nextConfig);
all pages are in the pages folder. Not the best solution for now, because it works in a deep up to like /pages/another-folder/file.
You can even get the lang param in your pages or _app.js:
....
const router = useRouter();
if(router.query.lang){
pageProps.lang = router.query.lang;
}
console.log(pageProps.lang);
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
For URL - /en/my-page, router.query.lang will be equal to en.
For URL - /my-page, router.query.lang will be undefined, but you can set a default lang.

Related

How to create a dynamic url with the # symbol

How would I create a dynamic route in Next.js with the # symbol?
For example localhost:3000/#some_username
I know you can create dynamic routes within the pages folder but how would I do it with just the # symbol and no / afterward like the above example.
You can add a prefix by defining a rewrite in your next.config.js.
module.exports = {
async rewrites() {
return [
{
source: '/#:username',
destination: '/users/:username'
}
]
}
}
This redirects all /#{username} routes to /users/{username}.
Some more information about rewrites in nextjs can be found here
It's the same as the standard dynamic routing in Next.js. You don't need rewrites.
Name the file or folder [#:username] in your root directory.
There are a few ways to do this:
App directory as folder named [#:username]
/app/[#:username]/page.js
Pages directory as file named [#:username].js
/pages/[#:username].js
Pages directory as folder named [#:username]
/pages/[#:username]/index.js
The param can be access the query param on the page like
import { useRouter } from "next/router";
const { query } = useRouter();
const username = query["#:username"];

How can I set the index page of my Gatsby site to be one of the dynamically generated pages?

I have a Gatsby site that queries information from a Wordpress REST API with GraphQL to dynamically create the site pages. I'd like to set my index page to be the homepage that is being created dynamically i.e home.html
I saw this post that was similar
On Gatsby CMS how can i set the about page as a index page
However, they have an about.js file that corresponds to their about page, meaning they can export it as a component and use it in index or they can even just copy the contents of that file over to index.js. The homepage that I want to set as my index is being generated dynamically and using a GraphQL query that can't be used outside of the page.js template. So I don't see an easy way to copy that over to another file.
I guess my last option would be to set my server to point to the static file in public/home.html and serve that as the site root, but the person in that posting tries to deter people from doing that.
Any ideas?
Here is page.js template that generates the pages of the site:
const PageTemplate = ({ data }) => (
<Layout>
{<h1 dangerouslySetInnerHTML={{ __html: data.currentPage.title }} />}
{
renderBlocks(gatherBlocks(data.currentPage.acf.page_blocks, data))
}
</Layout>
);
export default PageTemplate;
export const pageQuery = graphql`
query ($id: String!) {
currentPage: wordpressPage(id: {eq: $id}) {
title
id
parent {
id
}
template
acf {
page_blocks {
block_type {
acf_fc_layout
cs_title
cs_text
}
wordpress_id
}
}
}
}
`;
And here is my index page:
import React from "react"
import Layout from "../components/global/Layout"
const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to the Tank Gatsby site.</p>
<p>Now go build something great.</p>
</Layout>
)
export default IndexPage
I experienced the same situation today. I used the following approach to use my dynamically created page with uri '/home'(fetched from wordpress using GraphQL query) as the home page of my Gatsby site:
Delete the default index.js file in your pages directory.
In gatsby-node.js file, change the uri
of page from '/home' to '/' just before using the CreatePage API.
Here is the sample code to achieve the desired result:
// loop through WordPress pages and create a Gatsby page for each one
pages.forEach(page => {
if(page.uri==='/home/')
page.uri = '/'
actions.createPage({
path: page.uri,
component: require.resolve(`./src/templates/${page.template.templateName}.js`),
context: {
id: page.id,
},
})
})
In the above code, pages refer to the pages fetched from WordPress using GraphQL.
I could not find an easy way to create index page programmatically. Made it work nonetheless, details below.
createRedirect is valid approach but might affect SEO and definitely affects E2E tests cause actual page content gets rendered with a small delay.
Another thing to consider is that having pages/index.js file is required in order to get index.html file generated on production build. This gets in the way of using createPage({ path: '/', ... cause in my case programmatically created index page was overwritten by the static one (made of pages/index.js). This looks like a bug to me (or rather not supported feature). Corresponding github issue.
looks like deletePage and createPage gatsby-node APIs work asynchronously, hence we have to delete index page created from static file and create the one we want in the same callback. Not 100% sure about this one, but that's my observation.
onCreatePage API is a good candidate since it gets called upon original index page creation and we can take that one out and replace it with the custom one, programmatically created.
There is a catch however - CreatePageArgs interface (unlike CreatePagesArgs) doesn't provide reference to graphql, hence fetching data might be tricky.
Final solution:
export function onCreatePage(args: CreatePageArgs): void {
const { page } = args;
if (page.path === '/') {
const { deletePage, createPage } = args.actions;
const indexPageComponentPath = path.resolve(
'./src/pages/index.tsx',
);
deletePage({
path: '/',
component: indexPageComponentPath,
});
createPage({
path: '/',
component: yourComponentPath,
});
}
}
There is a solution: use createRedirect in gatsby-node.js.
E.g.:
index.tsx
import React from 'react'
export default () => <></>
gatsby-node.js
...
exports.createPages = async ({ actions }) => {
const { createRedirect } = actions
createRedirect({
fromPath: '/',
toPath: '/home',
isPermanent: true,
redirectInBrowser: true,
})
}
...
I was able to address this by copying the contents of the page.js template into index.js , but instead of using a regular GraphQL query, which cannot be used outside of the page template, I used useStaticQuery instead and hardcoded the id of the index page I was retrieving data from.

Next.js dynamic page params for static export

I have page which depends on route params (ex.: slug) like so http://example.com/blog/:slug. This route path is defined properly in my next.config.js file:
module.exports = withPlugins(plugins, {
exportPathMap: (defaultPathMap) => {
return {
'/': { page: '/home/home' },
'/blog/:slug': { page: '/careers/careers' }
}
}
});
This works fine when running the project in dev mode but once i export the project as static the route is not accessible and i get the regular 404 error from next.
Is there a way to fix this without using query parameters?
http://example.com/?slug=123
This solution https://github.com/zeit/next.js/blob/canary/examples/with-static-export/next.config.js is also not acceptable since the posts come from backend CMS
This is not possible because Next.js static export generates, well, static html pages. If you think about it, for this to work Next.js would somehow have to export every possible combination of letters valid in a url segment, which is not a good idea at all.
The closest you could get is using query parameters and the as attribute, for example when linking to a page:
<Link href='/blog/page?slug=SLUG_HERE' as='/blog/slug'>
// Link content here
</Link>
This only breaks when the user tries to link to or reload the page because there is no server-side support for the masking. You could theoretically use Nginx or Apache to proxy (is proxy the right word?) requests from /blog/SLUG_HERE to /blog/page?slug=SLUG_HERE. This is left up to you to figure out.
To handle dynamic paths in your next js project (provided you are going through the export route!).
Ensure trailingSlash is set to false or not defined at all in your next.config.js file
This way, every request will land in the index component, and from here, you can just handle your path redirect.
if (window.location.pathname !== "/") {
Router.push(window.location.pathname + window.location.search);
}
Ensure your project is mounted before doing this (e.g do this with useEffect hook)

Subroutes in Next Js

I´m new to next js, I have created a file called orders.js under pages directory, and I can access it correctly from localhost:3000/orders.
However, I want now to have a subroute, to access the order with id 1 (for example). So I have created a directory 'orders' inside the directory pages, and renamed order.js to index.js, after that, I have created another file inside the orders directory called id.js.
So my current structure is:
pages/
orders/
index.js
id.js
However I cannot access to localhost:3000/orders/1.
Using Nuxt js, this was trivial, how can I achieve the same with next.js ?
Thanks
This is also trivial with Nextjs, however, you're trying to achieve it the harder way.
Your first approach is correct. If you don't specify a route for your pages in the server.js file, Nextjs will automatically use them if the URL is correct (in this case orders leads to the orders.js page).
What you're looking for is to create a custom route. You can see the documentation for this here
I find the example in the documentation confusing, so I recommend using express instead. Here's an example for that. You can then see the express routes in the server.js file of the example.
Your route would end up looking something like this:
server.get('/orders/:id', (req, res) => {
return app.render(req, res, '/orders', req.query)
})
Where :id is a query param which you can then access in your getInitialProps inside your orders.js page.
You can check the express routing examples in the express documentation.
You can try using next-routes, dynamic routes for Next.js
And simply create a routes.js and add,
const routes = require('next-routes')
module.exports = routes()
.add('orders', '/orders/:id', 'orders/id')
// name, url, page folder
Or if you only want the server side routing,
server.get('/orders/:id', (req, res) => {
const actualPage = '/orders'
app.render(req, res, actualPage, req.query)
})
This might help you : https://nextjs.org/docs#dynamic-routing.
by adding [ ] to a page it creates a dynamic route, in this case [orderid].js can be used to map multiple orders to a single page.
pages/
orders/
[id].js
use
pages/
orders/
[dynamic_subroute].js
now catch it with
const router = useRoute();
const { dynamic_subroute } = router.query;
Now, you can catch the value (any) dynamically from the url which is used instead of dynamic_subroute
like- if the url is pages/orders/1
then value of dynamic_subroute will be 1 in your page

Prefix routes with locale in vue.js (using vue-i18n)

I have a locale.js file which is responsible for defining user locale. Here it is:
import store from '#/vuex/index'
let locale
const defaultLocale = 'en_US'
if (store.getters['auth/authenticated']) {
locale = store.getters['auth/currentUser'].locale || defaultLocale
} else {
if (localStorage.getItem('locale')) {
locale = localStorage.getItem('locale')
} else {
locale = defaultLocale
}
}
export default locale
Also I have a i18n.js file which is responsible for making i18n instance which I use when I init my app.
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import locale from '#/services/locale'
Vue.use(VueI18n)
const fallbackLocale = 'en_US'
let i18n = new VueI18n({
locale,
fallbackLocale,
})
i18n.setLocaleMessage('ru_RU', require('#/lang/ru_RU.json'))
i18n.setLocaleMessage('en_US', require('#/lang/en_US.json'))
export { i18n }
Now I think that it'd be more convenient to have URLs prefixed with locale, like /en/profile or /ru/profile. This way I can share a link with locale which would be already set.
Not sure how do to this though. Making all routes child and put /:locale? is not that convenient because router is not yet initialized (I pass i18n and router instances simultaneously when initing root app instance).
How can I achieve that, what would be the best approach?
You can implement router
routes: [{
path: '/:lang',
children: [
{
path: 'home'
component: Home
},
{
path: 'about',
component: About
},
{
path: 'contactus',
component: ContactUs
}
]
}]
and set locale in beforeEach hook
// use beforeEach route guard to set the language
router.beforeEach((to, from, next) => {
// use the language from the routing param or default language
let language = to.params.lang;
if (!language) {
language = 'en';
}
// set the current language for vuex-i18n. note that translation data
// for the language might need to be loaded first
Vue.i18n.set(language);
next();
});
There are two or three problems I can think of that comes with nesting all your routes under a single /:locale?.
Route definitions may become ambiguous. If you have paths /:locale?/foo/bar and /:locale?/bar defined as routes, what will <RouterLink to="/foo/bar" /> match? That will depend on which of those routes is defined first, and if the second of my examples is matched it will lead to an invalid locale. This problem has a simple-enough solution; just constrain your :locale parameter using a regex. If you know the exact list of supported locales statically, you could do something like:
import locales from '#/lang' // Your list of supported locales.
const regexp = locales.join('|') // You may want to filter out 'en' first.
const routes = [{
path: `/:locale(${regexp})?`,
children: [
...
],
}]
If your translations and list of supported locales are otherwise only available at runtime (e.g. they're retrieved via an API), you may be forced to create a regex specific to your locale tag format. If they match BCP-47, I believe that means either 2 or 3 characters for the primary subtag, and the script and region are optional. If you use normalized tags (lowercase primary, titlecase script, uppercase region), that's even better, because that will further reduce ambiguity:
const routes = [{
path: '/:locale([a-z]{2,3}(-[A-Z][a-z]+)?(-([A-Z]{2}|[0-9]{3}))?',
caseSensitive: true,
children: [
...
],
}]
You'll want to read the spec more closely than I have to ensure that regex is correct. You'll also need to guard against unsupported locales in your beforeEach hook, so that you can load a "Not found" error page.
As long as you do not define any routes whose first path segment could be mistaken for a locale tag, the above should fix the ambiguity problem.
Routes may accidentally be defined using root paths. Nested routes are usually defined using relative paths, i.e. paths not anchored with a /. However, nesting is not simply a mechanism for sharing prefixes or parameters among many routes, it is most often used for sharing layout components. Vue-router therefore allows you to override the parent route definition's path by defining an absolute path. The documentation explains:
Note that nested paths that start with / will be treated as a root path. This allows you to leverage the component nesting without having to use a nested URL.
Mistakenly defining an absolute path will cause the route to only be matched for the fallback (I assume English) locale. As developers are likely to prototype and test using English most of the time, it might not appear like anything is amiss.
For a small application where all your routes are defined within a single file, this may not be a big deal as the error is probably easy to spot. But for a large application with many route definition files and many developers, such an error is going to be more difficult to catch.
Every usage of <RouterLink> and programmatic navigation will require injecting the locale parameter. You'll need to remember to interpolate $i18n.locale into every to prop and push() call. Not doing so does not cause an error or break the page, so your tests are unlikely to catch this, and you won't notice any problems if you're only browsing in English. You could wrap or extend <RouterLink> with your own component that does this automatically, but that doesn't prevent someone from accidentally using RouterLink, as it is still globally-registered. You could also write a global mixin to add convenience methods for router.push()/.replace()/.go(), but this again would not protect you against accidental use of those methods.
One not-ideal solution to the above problems is to forego defining the locale as a path parameter, and instead match it prior to initializing the router. To do this, you have to pass it as the base constructor option. Unfortunately, the base path does not appear to be alterable, meaning locale changes will require a new page request. Since most users will likely change locale at most once, this might not be a huge problem, but nonetheless does not give the best user experience.

Categories

Resources