Case sensitive URL's with NextJS 10 problem - reactjs

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
}, [])

Related

In Next.js 13 app directory, how do I incrementally generate new pages?

I have multiple items from a CMS. /items/1 all the way to /items/9999. The content is immutable, so I don't have to worry about revalidateing them.
However, items do get added to the CMS frequently, maybe multiple times in a day. I want to make a static website. How can I add new static pages incrementally?
The CMS isn't handled by me, so there's no way I can add a hook.
As per the docs, by default, route segment parameters that were not statically generated at build-time by generateStaticParams function will be generated on demand. These non-generated segments will use Streaming Server Rendering. This is basically the equivalent to fallback: true on getStaticPaths function on pages folder page components.
Just make sure to perform the appropriate checks on your page component in case the requested data doesn't exist in the CMS. That way you can throw a Not Found error and render a 404 UI making use of the not-found.js file. Example from the docs:
import { notFound } from 'next/navigation';
export default async function Profile({ params }) {
const user = await fetchUser(params.id);
if (!user) {
notFound();
}
// ...
}

Routing localization with NextJS

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

What is proper way to detect device in Next.js SSR?

I have <MobileLayout />, <DesktopLayout />. I'm using Next.js for Server Side Rendering.
And I noticed there are many famous ui library has mobile detection components like <Respnosive /> component in Semantic-UI-React. But all of this is client side method, not working properly on SSR
I read some documents the conclusion is I should check user-agent of server side req.headers. In Next.js, What is proper way to detect device and conditonally render one of MobileLayout / DesktopLayout?
What I tried
in _app.js
import isMobile from 'ismobilejs'
...
function Homepage({ Component, pageProps, mobile }){
return (
mobile ?
<MobileLayout><Component {...pageProps} /></MobileLayout> :
<DesktopLayout><Component {...pageProps} /></DesktopLayout>
)
}
HomePage.getInitialProps = async (appContext) => {
const userAgent = appContext.ctx.req.headers['user-agent']
const mobile = isMobile(userAgent).any
const appProps = await App.getInitialProps(appContext)
return { ...appProps, mobile }
}
But the problem is getIntialProps on _app.js executed every page load. with moving page with client, the appContext.ctx is undefined so it will omit error. and I think this method might block some nextjs builtin optimizations.
Error in error page getInitialProps: TypeError: Cannot read
property 'headers' of undefined
So what is propery way to check device in Next.js?
If you want to detect the user's device using userAgent, your best bet is this answer:
IndexPage.getInitialProps = ({ req }) => {
let userAgent;
if (req) { // if you are on the server and you get a 'req' property from your context
userAgent = req.headers['user-agent'] // get the user-agent from the headers
} else {
userAgent = navigator.userAgent // if you are on the client you can access the navigator from the window object
}
}
(Note you should actually be using getServerSideProps or getStaticProps when possible, if you have Next 9.3 or newer, but sometimes there is no replacement for the getInitialProps functionality.)
However, the folks at Mozilla advise:
It's worth re-iterating: it's very rarely a good idea to use user
agent sniffing. You can almost always find a better, more broadly
compatible way to solve your problem!
The maker of the isMobile package you're importing even warns:
You might not need this library. In most cases, responsive design
solves the problem of controlling how to render things across
different screen sizes.
So, see if you can use CSS3 media queries to conditionally render certain elements or change their size, etc., rather than having completely separate mobile and desktop layout components. But it's possible you have an edge case where you can't make any alternative option work.
If you are going to keep your current setup and use your two layouts on other pages, you might consider combining them into a parent <Layout> component that conditionally renders one or the other so you don't have to copy that logic into every page:
export const Layout = (props) => {
return (
props.mobile ?
<MobileLayout>{props.children}</MobileLayout> :
<DesktopLayout>{props.children}</DesktopLayout>
)
}
you can use "next-useragent"
Give access to user-agent details anywhere using withUserAgent method.
next-useragent npm package

What is the best way to have a React-generated static (SEO) "public" frontend alongside a CRA "private" app?

I've been using Create-React-App and dig the whole setup, basically I'm looking to keep developing with JSX instead of switching to Gatsby/React-Static's Markdown/etc coding style. Similar question to this one regarding Gatsby. I'm looking to have a search engine optimized static "public" frontend (e.g. product pages, blog, etc.) that is generated by Gatsby or react-static. But I'd also like a typical client-side rendered React app for the "private" section (e.g. sellers can edit their product pages). I know you can set the "homepage" so I was thinking of setting the private section to "example.com/in/" or similar.
Any advice on how to best split this project?
If you aren't using the GraphQL stuff, Gatsby is predominantly just using React SSR and a custom webpack configuration with Reach Router and some glue to stick it all together.
You can absolutely have a separate Webpack configuration that outputs to your public folder and set up your host/deployment to route all of your non-static routes to your application entry.
You can also use Gatsby to generate all of those dynamic pages with juicy client-side fetches, and you basically get free static skeleton entry points for each of your pages like this:
const useMounted = () => {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
}, [])
return isMounted
}
const SomeComponent = props => {
const isMounted = useMounted()
return isMounted
? <div>Mounted client-side component</div>
: <SomeComponentSkeleton />
}
Effectively this just prevents issues with hydration that occur if you return null on server-side render and then return a component on the client-side. You can also use isMounted to kick off any code that uses window et al.

react router - keep the query string on route change

I would like to create routes that support query string.
When i say support i mean, passing it to the next route some how.
For example:
given this route: domain/home?lang=eng
and when moving to route domain/about i want it to keep the Query String and display domain/about?lang=eng.
I was sure there's a built in functionality for this but after reading the docs and a lot of search on the net, i couldn't find an elegant solution.
I'm using react-router#3.0.0 and react-router-redux#4.0.7
For react-router 4.x, try
const { history }
history.push('/about' + history.location.search)
To access this.props.history, make sure you have wrapped the component with withRouter HOC
import { withRouter } from 'react-router-dom'
...
export default withRouter(component)
refer https://github.com/ReactTraining/react-router/issues/2185
You will have to "forward" query param on each page transition - bothering and you can easily forgot to...
Instead, I would do this.
read stored/persisted lang preference. localStorage is good candidate here. Fallback to default language, when no preference is found
share lang via context, so that each and every component can read this value.
create some button (or whatever), which would modify this value
Since you are using redux, I would pull redux-persist to persist this preference across page reloads.

Resources