NextJS - window is not defined - next.js

I'm trying to import Typewriter Effect into my NextJS project, but whenever I do, I get this error that reads the following:
ReferenceError: window is not defined
and from what I've read, the error is displaying because it's trying to load the library on the server-side rather than the client-side.
So when I simply try to import it like this:
import Typewriter from 'typewriter-effect'
the error promptly displays.
People suggested that I try something like this:
let Typewriter
if (typeof window !== 'undefined') {
Typewriter = require( 'typewriter-effect' )
}
however, it doesn't work like this either. I get an error that reads the following:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
I've searched a lot of places for a potential solution to this problem, but I've been unsuccessful with my attempts.

What you need to do is dynamic import with No SSR
Try this:
import React, { Component } from 'react';
import dynamic from 'next/dynamic';
const Typewriter = dynamic(
() => import('typewriter-effect'),
{
ssr: false
}
)
class Home extends Component {
render() {
return (
<Typewriter
onInit={(typewriter) => {
typewriter.typeString('Hello World!')
.callFunction(() => {
console.log('String typed out');
})
.pauseFor(2500)
.deleteAll()
.callFunction(() => {
console.log('All strings were deleted');
})
.start();
}}
/>
)
}
}
export default Home;

Related

Composables inside composable

I'd like to use composables inside composables, but it turned out, that composables lose reactivity.
Please, give me advice how to tackle this problem.
The desired behavior is beneath:
import {ref, computed} from "vue";
import useFirstStep from "components/draft/use/useFirstStep";
import useSecondStep from "components/draft/use/useSecondStep";
export default function useFifthStep() {
const {isValidFirstStep} = useFirstStep();
const {isValidSecondStep} = useSecondStep();
const isValidStep = computed(() => {
return isValidFirstStep.value &&
isValidSecondStep.value &&
});
return {isValidStep};
}
It's hard to say without seeing the example code from useFirstStep and useSecondStep, but I bet you have a ref inside export, but you should keep it outside, so that whenever you use this composable you would not initialize given ref.
So useFirstStep.js should look like:
const isValidFirstStep = ref(false)
export default function useFirstStep() {
// some logic to set isValidFirstStep
return {isValidFirstStep};
}

How to get exports form mdx in nextjs

My Problem
The nextjs documentation on mdx states that it does not support frontmatter. Instead it is suggested to create a constant and export it [1]. However I can't seem to get at data exported in such a way. For instance using the following
/* -- ./pages/example.mdx -- */
export const meta = {
title: 'some example title'
}
/* -- ./pages/index.js -- */
import Example from './example.mdx';
export default function Index ({ ... props }) {
return <Example />
}
It seems that what gets imported can be used as a react component, but there does not seem to be a reference to the meta property anywhere.
Example does not have a meta property
import { meta } from './example.mdx does not yield anything
There is no meta key on the rendered components
Using require ('./example.mdx') yields the same results.
What I wanted to do
I have a list of markdown files and want to create an overview page that lists all of them, using the metadata defined in every file. Something akin to the following
import fs from 'fs/promises';
import path from 'path';
export async function getStaticProps () {
const root = path.join (process.cwd (), 'pages/items');
const listing = await fs.readdir(root);
const items = listing
.filter (item => item.endsWith ('.mdx'))
.map (item => {
const meta = require (`./items/${item}`).meta;
const id = item.replace (/\.md$/, '');
return { id, ... meta }
});
return { props: { items } };
}
export default function Overview ({ items, ... props }) {
/* ... use items */
}
Edit
It seems like there is a big difference between using .md and .mdx. In the examples I gave here I used .mdx, but locally I had used .md. Switching extensions makes everything work.
It is strange that the extension makes such a difference even though both of them are configured in next.config.js
const withMDX = require ('#next/mdx') ({
extension: /\.mdx?$/
});
module.exports = withMDX ({ /* ... */ });
[1] https://nextjs.org/docs/advanced-features/using-mdx#frontmatter
Use .mdx extension instead of a .md extension.
Seems like you can create a typing file to import it if you are using typescript
Step 1 - Create typing file
declare module '*.mdx' {
export const meta: {
title: string
}
}
Step 2 - import the exported content
import Example, {meta} from './example.mdx';
Got the answer from here https://gist.github.com/peterblazejewicz/1ac0d99094d1886e7c9aee7e4faddef3#file-index-d-ts-L68

getStaticPath and the need for filetype in the URL

In /pages I have [page].js and index.js.
[page].js generate needed Pages by the Value of "CustomPage". It's content comes from an Data-JSON-File.
It work like expected, as long as I start on the Homepage and use links inside of my Webpage.
For example I have 2 Pages for now: /impressum and /datenschutz.
So clicking the link "Impressum" open myDomain.com/impressum (and it work, BUT notice, there is no .html at the end).
BUT, if I refresh the page, or type myDomain.com/impressum directly in the addressbar of the browser, I got an not found error (from nginx-server, not from next!).
Second try
As I need a fully static page and I've added getStaticPath and getStaticProps in the file for testing purposes, so that "real" html-files will be created:
import { useRouter } from 'next/router';
import Index from './index';
import config from '../content/config.yml';
import CustomPage from '../src/components/CustomPage';
const RoutingPage = () => {
const { customPages } = config;
const router = useRouter();
const { page } = router.query;
const findMatches = (requestedPage) =>
customPages.find((customPage) => customPage.name === requestedPage) ||
false;
const customPageData = findMatches(page);
if (customPageData !== false) {
return <CustomPage pageContext={customPageData} />;
}
return page === 'index' ? (
<Index page={page} />
) : (
<p style={{ marginTop: '250px' }}>whats up {page}</p>
);
};
export async function getStaticPaths() {
return {
paths: [
{ params: { page: 'impressum' } },
{ params: { page: 'datenschutz' } },
],
fallback: false, // See the "fallback" section below
};
}
export async function getStaticProps({ params }) {
return { props: { page: params.page } };
}
export default RoutingPage;
This generates the single pages as real html-files:
But this lead me to the next issue:
I've implemented internal Links in the Webpage like this:
which still lead a user to myDomain.com/impressum, now additionally there is myDomain.com/impressum.html available. From SEO perspective, this are two different paths.
How do I get them unified, so that I have only one path - regardles of whether if I open it from within my Webpage, or enter it directly.
Workaround Idea (??)
Sure, I could everywhere use something like:
<Link href={`/${item.page}.html`}>
But this only work if the Page is exported and copied to the Server. For next dev and next start this won't work, because the .html-File don't exist.... and so I'll lost the "page preview" while working at the page.
So only Idea I have is to set an ENV-Variable for .env.development & .env.production and encapsulate the -Component from NEXT in a HOC.
In that HOC I could check if I'm currently in dev or prod and don't use .html for those links... otherwise add the .html to the link.
What YOU say about this. Do you have any other solution?
I don't know if it's state of the art, but as little workaround I did this:
I place the next/link-Component in a HOC and check if it's run on development or production (process.env.NODE_ENV):
import React from 'react';
import Link from 'next/link';
const LinkHoc = (props) => {
const { as, href, children } = props;
if (process.env.NODE_ENV === 'production') {
return (
<Link
{...props}
as={as ? `${as}.html` : ''}
href={href ? `${href}.html` : ''}
/>
);
}
return <Link {...props}>{children}</Link>;
};
export default LinkHoc;
With this workaround you get mydomain.com/impressum links in DEV and mydomain.com/impressum.html in production.
Only thing what to do at least is to rename the JSON-Files for the generated pages.
They are in /out/_next/data/XYZranadomString/.
They are named like impressum.json and you need to rename it to impressum.html.json to fix the 404 error on clientside for this files.
Would love to see a better Solution, so if you have any suggestions, please let me know!

Nextjs page goes to 404 on refresh

I'm using nextjs and graphql for a shopify POC.
I have a component that shows a list of products with links on them that point to the product page
<Link
as={`${handle}/product/${url}`}
href={`/product?id=${item.id};`}>
<a>{item.title}</a>
</Link>
handle is the collection name so the url in the browser will look like
http://localhost:3000/new-releases/product/Plattan-2-Bluetooth but behind the scenes its really just using a page called products and i'm passing the product id.
Now in product.js (pasted below) i'm getting the query string value of the id and doing another query to get the product. All works fine but then if i hit refresh or copy and paste the url into a new window i get 404.
I know this is something to do with routing but i'm not sure what i need to do to fix this. Thanks
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
class product extends Component {
static async getInitialProps({query}) {
console.log("query", query)
return query ? { id: query.id.replace(';', '') } : {}
}
render() {
const PRODUCT_FRAGMENT = gql`
fragment ProductPage on Product {
title
description
descriptionHtml
id
images(first: 10, maxWidth: 600) {
edges {
node {
id
altText
originalSrc
}
}
}
}
`;
const PRODUCT_FETCH_QUERY = gql`
query PRODUCT_FETCH_QUERY {
node(id: "${this.props.id}") {
__typename
...ProductPage
}
}
${PRODUCT_FRAGMENT}
`;
return (
<div>
<Query query={PRODUCT_FETCH_QUERY}>
{({ data, error, loading }) => {
console.log("data", data)
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
return null}
}
</Query>
</div>
);
}
}
product.propTypes = {
};
export default product;
You can try these in a file called next.config.js in the root of your project
module.exports = {
trailingSlash: true
}
Check this link
This is because when you use the next/link component the href prop has the "real" URL to the page with the query parameter for the item ID set. This means that on the client (the browser), Next.js can load the right page (your product.js page) with the parameter for your data query.
But when you load from the server, either by reloading the page or opening it in a new window, Next.js doesn't know what page to load and in this case I think it will try to find the file ./pages/new-releases/product/Plattan-2-Bluetooth.js, which of course doesn't exist.
If you want to have these kinds of URLs you have to make sure the request gets routed to the right page file (./pages/product.js) on the server as well. You can do this by by creating a custom server. There are a bunch of examples in the Next.js repo including one using Express. This is also covered in the "Learn" section of there website in a tutorial called Server Side Support for Clean URLs
If you decide to use Express, you will end up with something like:
server.get('/:collection/product/:productUrl', (req, res) => {
return app.render(req, res, '/product', { productUrl: req.params.productUrl})
})
This will render the product page, and you'll have productUrl available on the query object in getInitialProps(). Of course now you will need to fetch your data with that instead of the product id.

Restrict Access (Meteor + React Router + Roles)

I am trying to implement alanning Meteor-roles with react-router in my Meteor application. Everything is working fine except the fact I can't manage properly to restrict a route using alanning roles or Meteor.user()
I tried with meteor-roles:
I am trying to use the onEnter={requireVerified} on my route. This is the code:
const requireVerified = (nextState, replace) => {
if (!Roles.userIsInRole(Meteor.userId(), ['verified'],'user_default')) {
replace({
pathname: '/account/verify',
state: { nextPathname: nextState.location.pathname },
});
}
};
I tried with Meteor.user():
const requireVerified = (nextState, replace) => {
if (!Meteor.user().isverified == true) {
replace({
pathname: '/account/verify',
state: { nextPathname: nextState.location.pathname },
});
}
};
So this is working when I am clicking on a route link, but when i manually refresh (F5), it does not work. After digging into it, i have found that Meteor.user() is not ready when i manually refresh the page.
I know Meteor.userid() or Meteor.logginIn() are working, but i wanted
to verify not just that they are logged but if they are "verified" or
have a role.
I also tried to check inside the component with react, with componentDidMount() or componentWillMount(), in both cases it's the same, the manual fresh does not load Meteor.user() before the compenent is mounted.
So what is the best way to restrict components/routes with meteor/alaning roles + react router ? (I am using react-komposer inside TheMeteorChef's base)
Thank you.
Note I have not tried it yet, it's only a suggestion
One thing you could try is to use componentWillReceiveProps alongside createContainer from 'react-meteor-data' like that:
import React, { Component, PropTypes } from 'react';
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import { Roles } from 'meteor/alanning:roles';
class MyComponent extends Component {
componentWillReceiveProps(nextProps) {
const { user } = nextProps;
if (user && !Roles.userIsInRole(user._id, ['verified'], 'user_default')) {
browserHistory.push('/account/verify');
}
// If Meteor.user() is not ready, this will be skipped.
}
}
MyComponent.propTypes = {
user: PropTypes.object,
};
export default createContainer(() => {
const user = Meteor.user() || null;
return { user };
}, MyComponent);
To explain the flow, when the page is loaded, as you said Meteor.user() is not defined so you can't check the permissions. However, when Meteor.user() gets defined, this will trigger a refresh of the template, and the new props will be passed to componentWillReceiveProps. At this moment you can check if user has been defined and redirect if needed.
To be really sure not to miss anything, I would actually put the verification in the constructor() as well (defining a function that takes the props as arguments and calling it in both constructor() and componentWillReceiveProps()).

Resources