Cannot GET from correct location with axios in nuxt - javascript

The issue I am having seems to be that axios is ignoring my GET path when published to build, but works as expected in dev mode.
I am using the following to retrieve local .html files and parse them into my vue component.
<template>
<div>
<div v-html="message"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data () {
return {
message: '',
}
},
async mounted(){
const mypath = 'http://***.com/posts/' + this.$route.params.post + '.html';
const ip = await this.$axios.$get(mypath);
this.message = ip;
}
}
</script>
This .vue is located at "pages/blogs/posts/_post.vue"
My .html files are located in "static/posts/examplepost.html"
When running in dev mode, if I change the first part of the path to either "http://localhost:3000/posts/" or just "/posts/", everything works perfect.
However, when I publish to the web and link to a blog post url, I get a 404 at the following path: "https://***.com/blogs/posts/examplepost.html"
Why is this working in dev mode but not when built?
I have tried moving the posts into the "/blogs/posts/" location and have tried creating a new instance of axios and setting the baseURL directly in my component and get the same results.

Found the problem.
The issue was that I was using <a> in order to create my links to the individual pages created by the "_post.vue"
<a :href="'posts/' + blog.link">
Using <nuxt-link>, like it is recommended everywhere, fixed the issue:
<nuxt-link :to="'posts/' + blog.link">

Related

Installing Plaiceholder in Next.js / Webpack 5 causes: Module not found: Can't resolve 'child_process'

I'm building on Next.js app and when I install / import Plaiceholder (for generating placeholder images), I get the following error: Module not found: Can't resolve 'child_process'
Node v14.18.0
Next.js v11.1.2
Plaiceholder v2.2.0
Sharp v0.29.2
I understand this error message to mean that webpack5 is trying to bundle node packages that aren't available to the client. But I'm not clear why it is doing this. I haven't customized any of the webpack configs, and I can't find any mention of this issue in the Plaiceholder docs. How do I fix it?
NOTE: I want the base64 data URL to get created during the build, so that it available as soon as the page loads (not fetched asynchronously at run time).
Here's my next.config.js
module.exports = {
reactStrictMode: true,
};
My package.json only has scripts, dependencies, and devDependencies (nothing to change module resolution)
In case it's relevant, here's a simplified example using Plaiceholder:
import Image from "next/image";
import { getPlaiceholder } from "plaiceholder";
import React, { useState } from "react";
...
const { base64 } = await getPlaiceholder(imgUrl);
...
return (<Image
src={imgUrl}
placeholder="blur"
blurDataURL={base64}
/>);
It seems like plaiceholder is not suitable for client-side rendering. I believe that package is for the Node.js environment. That's why you get this error when you try to render your component on the client side.
To solve this problem, you need to move import { getPlaiceholder } from 'plaiceholder' to the NextJS API section. Then you can call that API with your URL data in the body. Then get the base64.
/api/getBase64.js
import { getPlaiceholder } from "plaiceholder";
export default async (req, res) => {
const { body } = req;
const { url } = body;
const { base64 } = getPlaiceholder(url);
res.status(200).send(base64);
};
/component.js
import Image from "next/image";
import React, { useState, useEffect } from "react";
const [base64, setBase64] = useState()
useEffect(() => {
(async () => {
const _base64 = await fetch.post('/api/getBase64', {url: imgUrl}); // wrote for demonstration
setBase64(_base64);
})()
})
return (<Image
src={imgUrl}
placeholder="blur"
blurDataURL={base64}
/>);
I know blurDataURL will be undefined until you fetch the data but this is the way how you can use plaiceholder library to manage your images. It should be imported only for the NodeJS environment. If you do not like this approach, you can try to find another library that also works for the browser environment (client)
UPDATED according to the comment:
If you want to generate this base64 at build time, you can use getStaticProps in the pages that use this Image component. NextJS is smart enough to understand which libraries are used in the client-side or server-side. So you can do this:
import { getPlaiceholder } from "plaiceholder"; // place it at the root of file. This will not be bundled inside of client-side code
export async function getStaticProps(context) {
const { base64 } = await getPlaiceholder(imgUrl);
return {
props: { base64 }, // will be passed to the page component as props
}
}
This way, by using getStaticProps, the page will be created at build time. You can get the base64 prop inside of the page that uses the image component and pass that prop to blurDataURL. Also, you can use this approach with getServerSideProps too.
This is from NextJS website:
Note: You can import modules in top-level scope for use in
getServerSideProps. Imports used in getServerSideProps will not be
bundled for the client-side.
https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
It's necessary to Install plugin for Next Js dependency and configure next config based on Plaiceholder Docs for using getPlaiceholder() function in getStaticProps like the answer by #oakar.
npm i #plaiceholder/next
const { withPlaiceholder } = require("#plaiceholder/next");
module.exports = withPlaiceholder({
// your Next.js config
});

Trouble with using an npm package (leaflet addon) in html <script> tag

I am trying to include a leaflet package in my locally hosted website. The idea of the website is to allow the user to query for tweets from the Twitter API (the request is sent fro the HTML page to a NodeJs server running on express) and have these sent back and loaded in the leaflet map using a bounding box to place the marker. Since most tweets don't come with a geotag or bounding box information, I thought the next best thing would be to take the name of the location (e.g. London) stored in the tweet metadata and do a reverse geocoding to get the bounding box from the city/location name. To do this reverse geocoding I thought I would use a leaflet add-on called leaflet-geosearch, since the other common option of using the Google Maps API seem not to be free. I have tried now many ways of importing this package into my project, but all with no luck. The documentation shows that it can be loaded using
// import
import { OpenStreetMapProvider } from 'leaflet-geosearch';
// setup
const provider = new OpenStreetMapProvider();
// search
const results = await provider.search({ query: input.value });
So I tried to load it in this fashion in my HTML code inside of a tag as such:
<script type="module">
import { OpenStreetMapProvider } from 'leaflet-geosearch';
// setup
const provider = new OpenStreetMapProvider();
</script>
But it tells me that I need to add a '/' for a relative pathway. So I tried to load it using a relative path to numerous different scripts in the node_modules/leaflet-geosearch folder:
<script type="module">
import { OpenStreetMapProvider } from './node_modules/leaflet-geosearch/dist/geosearch.js';
// setup
const provider = new OpenStreetMapProvider();
</script>
The above method told me that "'geosearch.js' does not provide an export named 'OpenStreetMapProvider'", so I went back and looked through the files some more and found a file named 'openStreetMapProvider.js' and so tried to load this:
<script type="module">
import { OpenStreetMapProvider } from './node_modules/leaflet-geosearch/lib/providers/openStreetMapProvider.js';
// setup
const provider = new OpenStreetMapProvider();
</script>
But it gave me the same error "... does not provide an export named 'OpenStreetMapProvider'".
I have tried to load it directly into my nodeJS server script,
// import
const OpenStreetMapProvider = require('leaflet-geosearch');
// setup
const provider = new OpenStreetMapProvider();
But when I then try to run the server on a local port it throws the Reference error below.
Does anyone know how I could properly load and use this module in my HTML page? I have seen some answers that say browserify can help in such cases, but if that was really necessary then the ReadME on the package's github page should have mentioned this, so I feel like there must be a more straightforward solution to this.

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.

Nuxt Dynamic Routing on Firebase

My Nuxt.js App has this structure:
/pages/index.vue
/pages/_slug/index.vue
When user gets /{any_page}, it will use the path to build the page content:
/pages/_slug/index.vue
<template>
<div>
{{slug}}
</div>
</template>
<script>
import fetch from 'isomorphic-fetch';
export default {
async asyncData({ params }) {
return { slug: params.slug }
}
}
</script>
This works perfectly when running the Nuxt App directly via yarn dev.
When I try to run this using firebase functions:
$ firebase serve --only functions,hosting
The static routes work perfectly, but the dynamic routes always render the default / page, instead of executing the dynamic one. How do I fix this?
If using nuxt generate, then the explanation below should work. If using nuxt build, look at this repo, it is a template i wrote for nuxt + firebase as ssr.
Generate says it skips over dynamic routes, unless you explicitly specify them, hence 404 errors. In your Nuxt config file, you want to specify those routes. Here is an example of fetching from the database during generation. Note that in my example below, the doc id is the slug for the page.
nuxt.config.js
generate: {
async routes() {
const { db } = require('./services/fireInit');
const qs = await db.collection('recipes').get();
return qs.docs.map(x => `/recipes/${x.id}`);
}
},
The downside to this approach is as you add new items via a CMS, you will need to rebuild/deploy your site each time if you want to be able to use these new links without a 404. CMS without redeploy does work if you navigate to new content 'organically'...404 only occurs on a page refresh or on jumping directly to the page from a hyperlink.

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)

Categories

Resources