Getting the Site URL in Next.JS [duplicate] - javascript

I want to get the page's full URL or site hostname like the image below on Static Site Generator.
I will try with window.location.hostname, but it doesn't work.
The error: window not defined.

If you want the hostname inside getInitialProps on server side, still you can get it from req
Home.getInitialProps = async(context) => {
const { req, query, res, asPath, pathname } = context;
if (req) {
let host = req.headers.host // will give you localhost:3000
}
}

With server-side rendering (getServerSideProps), you can use context.req.headers.host:
import type { GetServerSideProps, NextPage } from "next";
type Props = { host: string | null };
export const getServerSideProps: GetServerSideProps<Props> =
async context => ({ props: { host: context.req.headers.host || null } });
const Page: NextPage<Props> = ({ host }) => <p>Welcome to {host || "unknown host"}!</p>;
export default Page;
But with static generation (getStaticProps), the hostname is not available, because there is no request to get it from. In general, a server doesn't know its own public hostname, so you need to tell it. Using Next.js environment variables, put this in .env.local:
HOST=example.com
Then access it with process.env['HOST']:
import type { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps<Props> =
async context => ({ props: { host: process.env['HOST'] || null }});

If you want to get the full URL:
import { useRouter } from 'next/router';
const { asPath } = useRouter();
const origin =
typeof window !== 'undefined' && window.location.origin
? window.location.origin
: '';
const URL = `${origin}${asPath}`;
console.log(URL);

The place where you are accessing the window make sure you add a check so that code is executed only on the browser and no during SSG"
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
}
Update:
If you have specified basePath in next.config.js:
module.exports = {
basePath: 'https://www.example.com/docs',
}
Then using useRouter, you can access the base path:
import { useRouter } from 'next/router'
function Component() {
const router = useRouter();
console.log({ basePath: router.basePath});
// { basePath: 'https://www.example.com/docs' }
...
}
But if you have a relative base path then you can use the first approach

Consider this package > next-absolute-url
import absoluteUrl from 'next-absolute-url'
const { origin } = absoluteUrl(req)
const apiURL = `${origin}/api/job.js`
If you deployed your Next.js app with now the apiURL will be something like https://your-app.now.sh/api/job.js.
However, if you are running the app locally the apiURL will be http://localhost:8000/api/job.js instead.

Using typeof window !== 'undefined' is the secure way. if (window) {} will run you into problems.
const hostname = typeof window !== 'undefined' && window.location.hostname ? window.location.hostname : '';
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
Using above code will give you the frontend/outside hostname/origin the client using: example.com, www.example.com, www.example.com:80 and so on, not the localhost stuff. useRouter() will return the server side hostname/origin (localhost, localhost:3000)

I believe you're better of doing this with a combination of useRouter and useEffect hooks. In my case I wanted to dynamically set the og:url of my webpage. This is what I did. We have router.pathname as a dependency so that ogUrl is updated every time we move to a different page.
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const MyComponent = () => {
const router = useRouter();
const [ogUrl, setOgUrl] = useState("");
useEffect(() => {
const host = window.location.host;
const baseUrl = `https://${host}`;
setOgUrl(`${baseUrl}${router.pathname}`);
}, [router.pathname]);
return <div></div>
}

You need to ensure your access to window.location.hostname happens on the client-side only, and not during server-side rendering (where window does not exist). You can achieve that by moving it to a useEffect callback in your component.
function Component() {
useEffect(() => {
console.log(window.location.hostname)
console.log(window.location.href) // Logs `http://localhost:3000/blog/incididunt-ut-lobare-et-dolore`
}, [])
// Remaining code of the component
}

req.headers are Symbols and not Objects, so to get value, you use the get method
const host = req.headers.get("host"); // stackoverflow.com

AFAIK there are two ways of doing this:
Next JS provides us with the useRouter hook, first you have to import it in your component, then, to use the router object, you just have to declare it. For example:
const router = useRouter();
console.log(router.pathname);
const {pathname} = router; <---- To access the pathname directly.
Besides this, as #Xairoo said before, if you want to use the window object, you have to check if window !== 'undefined' to avoid errors. The window not defined error happens because Next JS use NodeJS to render the app and the window object is not defined in Node JS.
You can find a more detailed explanation in this link.

none oh the answers above solved the problem and this is the solution i figured it out :
function return_url(context) {
if (process.env.NODE_ENV === "production") {
// if you are hosting a http website use http instead of https
return `https://${context.req.rawHeaders[1]}`;
} else if (process.env.NODE_ENV !== "production") {
return "http://localhost:3000";
}
}
and on the getServerSideProps or getStaticProps functions you use
export async function getServerSideProps(context) {
let url = return_url(context);
const data = await fetch(`${url}/yourEndPoint`).then((res) => res.json());
return {
props: {
data: data,
},
};
}

Using a middleware.js file that you add to the root of your project can give you access to the host name and provide a lot of flexibility to perform actions based on it if needed.
https://nextjs.org/docs/advanced-features/middleware
// Example: redirecting a domain to a subdomain
import { NextResponse } from "next/server";
// This function can be marked `async` if using `await` inside
export function middleware(request) {
// Currently there is no main site so we redirect to the subdomain.
const host = request.headers.get("Host");
if (
process.env.NODE_ENV === "production" &&
host.startsWith("mydomain.com")
) {
return NextResponse.redirect(new URL("https://mysubdomain.mydomain.com"));
} else if (
process.env.NODE_ENV === "staging" &&
host.startsWith("staging.mydomain.com")
) {
return NextResponse.redirect(
new URL("https://mysubdomain-staging.mydomain.com")
);
}
}

in Next.js you can do like this,
by useEffect to get window.location.origin in client side,
and set it to state.
work fine in :
{
"next": "12.1.6",
"react": "18.1.0",
}
const Home: NextPage = () => {
const { asPath, query } = useRouter();
const [loading, setLoading] = useState(false);
const [loginCallBackURL, setLoginCallBackURL] = useState("");
useEffect(() => {
setLoginCallBackURL(
`${window.location.origin}/${query.redirect ? query.redirect : "user"}`,
);
}, []);
// if you do something like this, it can't get loginCallBackURL
// const loginCallBackURL = useMemo(() => {
// if (typeof window !== 'undefined') {
// return `${window.location.origin}/${
// query.redirect ? query.redirect : "user"
// }`;
// }
// return asPath;
// }, [asPath, query]);
return (
<div>
<Button
variant="contained"
href={queryString.stringifyUrl({
url: `${publicRuntimeConfig.API_HOST}/auth/google/login`,
query: {
callbackURL: loginCallBackURL,
},
})}
>
Sign in with google
</Button>
</div>
);
};
export default Home;

We can get current url like this:
import { useRouter } from 'next/router';
const router = useRouter();
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
const address_url = origin+router.asPath;

Related

Get the hostname inside getStaticProps

Is it possible to get the hostname inside getStaticProps?
export async function getStaticProps(context) {
// get the hostname
const hostname = ?????
}
It's not possible to get the hostname inside getStaticProps as the props generated there are static and do not contain information on the request in any way. To get the hostname, you will have to use getServerSideProps or get the hostname on the client side in the page's component. Here is an example of getting the hostname in getServerSideProps:
export const getServerSideProps: GetServerSideProps<{ host: string; }> = async (context) => {
return {
props: { host: context.req.headers.host || null }
};
};
Here is an example of getting the hostname on the client side component using Location:
function Home() {
// window can be undefined when static generated, however it will be updated when the page is hydrated
const hostname = typeof window !== 'undefined' ? window.location.hostname : '';
}
export default Home;
create function :
export const hostname = "https://urldomain.com"
and import everywhere
import {hostname} from "./hostname";
export async function getStaticProps(context) {
// get the hostname
console.log(hostname)
}

CoinbaseWalletSDK does not work with server side rendering

I was trying to load CoinbaseWalletSDK in my NextJS application, but it always throw an error of ReferenceError: localStorage is not defined due to it was imported before the window is loaded. I tried dynamic loading but it doesn't work. The following is what I am using at this moment.
export async function getServerSideProps({
params,
}: {
params: { project_id: string };
}) {
const project_id = params.project_id;
let project: any = fakeProjects[0];
if (project_id && typeof project_id === 'string' && !isNaN(parseInt(project_id))) {
const id = project_id;
project = fakeProjects.find(p => p.id === parseInt(id));
// Fetch project detail here
let item = await (
getNFTStatsByProjectId(
parseInt(project_id)
)
);
if (project && item && item['nftTotal'] && item['nftSold']) {
if (item.nftSold > item.nftTotal) {
item.nftSold = item.nftTotal;
}
project.nftTotal = item.nftTotal;
project.nftSold = item.nftSold;
}
}
const { coinbaseEth } = (await import('../../components/services/coinbase'));
return {
props: {
project: project,
coinbaseEth: coinbaseEth
},
};
}
And this is what I have in the coinbase service:
// TypeScript
import CoinbaseWalletSDK from '#coinbase/wallet-sdk'
import Web3 from 'web3'
const APP_NAME = 'Practice App'
const APP_LOGO_URL = process.env.WEBSITE_URL + '/logo.png'
const DEFAULT_ETH_JSONRPC_URL = 'https://mainnet.infura.io/v3/' + process.env.INFURA_PROJECT_ID
const DEFAULT_CHAIN_ID = 1
// Initialize Coinbase Wallet SDK
export const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
darkMode: false
})
// Initialize a Web3 Provider object
export const coinbaseEth = coinbaseWallet.makeWeb3Provider(DEFAULT_ETH_JSONRPC_URL, DEFAULT_CHAIN_ID)
// Initialize a Web3 object
export const web3 = new Web3(coinbaseEth as any)
The new CoinbaseWalletSDK is where the error was thrown if that's a concern.
Based on my research, I will need to get it imported after the page is fully loaded (which is the point when "window" become available, as well as "localStorage"), which I have no clue how to achieve. Can anyone help me out on this?
I solved it by loading it later. What I did was to assign this variable with this function.
setTimeout(async () => {
coinbaseEth = (await import('../../components/services/coinbase')).coinbaseEth;
}, 1000)
I choose not to use useEffect because the value will be lost on render, which prevents the function to work properly.

How to get global.window.localStorage inside getServerSideProps

I have some component which get props with data and render it data. In my getServerSideProps function I need to get data from localStorage, but I can't do it because of window is undefined.
I tried using if (typeof window !== 'undefined') but it's still not working. What's wrong with my code?
const MainComponent = ({serverResponseData}) => {
// ... some code
console.log(serverResponseData))
// ... some code
}
export async function getServerSideProps() {
let filterId = []
if (typeof window !== 'undefined') {
filterId = global.window?.localStorage.getItem('filterId') || '';
}
// ...some code, and filterId variable still empty array
const scoresRes = await fetchData('scores',{filterId});
return {
props: {scoresRes}
};
}
I'm also tried use useEffect, but got error
React Hook "useEffect" is called in function "getServerSideProps" that
is neither a React function component nor a custom React Hook
function.
Referring to the documentation
If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
Localstorage is only available on the client side and you are trying to access it in a server side only function , you can use something like
if (typeof window !== 'undefined') {
// your code
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
}
Please review this article to get more information on running client side only code.
Another approach would be to use a dynamic import where the hello3 component would contain the code accessing local storage.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
getServerSideProps only trigger on the server-side and it won't be available on the client-side
getServerSideProps only runs on server-side and never runs on the browser.
If you want to have that storage logic, I'd suggest you use cookies instead. Not like localStorage, cookies are available on both server-side and client-side.
You can modify your logic like below with cookies
export async function getServerSideProps(context) {
const filterId = context.req.cookies.filterId || ''; //get filterId from cookies
// ...some code, and filterId variable still empty array
const scoresRes = await fetchData('scores',{filterId});
return {
props: {scoresRes}
};
}
Another possible approach is using getInitialProps which is available on both sides
MainComponent.getInitialProps() {
let filterId = []
if (typeof window !== 'undefined') {
filterId = global.window?.localStorage.getItem('filterId') || '';
}
// ...some code, and filterId variable still empty array
const scoresRes = await fetchData('scores',{filterId});
return scoresRes;
}

Shopify script tag not rendering

the problem
I created a Shopify node.js app using the Shopify CLI and I want to display a simple bar under the header using a script tag. I used the script tag API to add a script tag
"script_tags": [
{
"id": 174240039086,
"src": "https://xxxxx.ngrok.io/script_tag",
}
]
And I also added a <div id="script-app"></div> into the theme, under the header.
Here is my script_tag.js file, located in /pages/script_tag.js
import ReactDOM from 'react-dom';
class TestScriptTag extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<p>this is a bar</p>
</div>
);
}
}
ReactDOM.render(<TestScriptTag />, document.getElementById('script-app'));
export default TestScriptTag;
Lastly, here is my server.js (most of it is what came with the CLI):
import "#babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "#shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "#shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import { flushSync } from "react-dom";
const fs = require('fs');
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8083;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: false,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
async afterAuth(ctx) {
console.log("here")
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`);
},
})
);
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
};
router.get("/", async (ctx) => {
const shop = ctx.query.shop;
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
router.get("/script_tag", (ctx) => {
handleRequest(ctx);
});
router.get("(/_next/static/.*)", handleRequest); // Static content is clear
router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
I am getting the error: document not defined.
What I've tried
I thought this is due to server side rendering, so I thought I could get around it by doing this:
if (typeof window !== "undefined") {
ReactDOM.render(<TestScriptTag />, document.getElementById('script-app'));
}
But still nothing renders and I get this when I inspect the shop page.
I've also tried changing the routing to this:
router.get("/script_tag", (ctx) => {
ctx.type = "module";
ctx.body = fs.createReadStream('./pages/script_tag.js')
});
But then I get an error about the import statement in script_tag.js - SyntaxError: Unexpected identifier '{'. import call expects exactly one argument.
I'm not sure what the proper way is to serve the javascript file I want to inject into the header. I feel like I'm missing something stupid. Please help!!

How to get absolute URL in production?

The problem I'm facing is that I'm unable to get the absolute URL in the production build when using getStaticPaths and getStaticProps
export async function getStaticPaths() {
const url =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://websitename.vercel.app";
const res = await fetch(`${url}/api/posts`);
const posts = await res.json();
console.log("posts: ", posts);
const paths = posts.map(({ slug }) => ({
params: { slug },
}));
console.log("Paths: ", paths);
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
console.log("params: ", params);
const url =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://websitename.vercel.app";
const res = await fetch(`${url}/api/post`, {
method: "POST",
body: params.slug,
});
const post = await res.json();
return {
props: { post },
};
}
It works fine in the development build but when it comes to production it fails because the hardcoded https://websitename.vercel.app is not the one generated by vercel. The URL generated by vercel is something like this websitename-q1hdjf6c2.vercel.app.
How do I fix this?
you can use ${process.env.VERCEL_URL}/my/route.
Check
Vercel environment variables
Unfortunately, since static site building process is done without an actual site running, the full URL is not accessible there. You can only access the full URL on the client side in the global window object.
You can also set an environment variable after Vercel generates a URL for your application and use it from there.
This seem to work
import { useRouter } from "next/router";
...
const router = useRouter();
...
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
url = origin + router.asPath;
or
import { useRouter } from "next/router";
...
const {asPath} = useRouter();
...
const url = process.env.NEXT_PUBLIC_DOMAIN + asPath;
...
.env
NEXT_PUBLIC_DOMAIN='https://webaddress.com'

Categories

Resources