How to cache digitaloceanspaces images with Next/Image - caching

I am using the new next/image component to load avatar images from digitaloceanspaces as follow:
import Image from "next/image";
...
<Image
layout="fill"
quality={1}
loader={avatarLoader}
src="839e7be6-1d2d-4164-bd33-befc3a7613c6_1610115441"
/>
The loader looks like this:
export const avatarLoader = ({ src, width, quality }: ImageLoaderProps) => {
return `https://donout.ams3.digitaloceanspaces.com/${src}?w=${width}&q=${quality || 75}`;
};
But It's not caching it. And I try adding it on my next.config
module.exports = {
async headers() {
return [
{
source: "https://donout.ams3.digitaloceanspaces.com/*",
headers: [
{
key: "Cache-Control",
value:
"public, max-age=31536000, s-maxage=31536000, stale-while-revalidate",
},
],
},
];
},
};
But nothing is working. Can anyone tell me how to cache images from remotes urls?

Related

SCSS Modules not properly loading in Storybook for my Nextjs app

Working on a new project using Nextjs and Storybook. We are using SCSS modules to style our components, and they work just fine in the actual app on the browser, but they won't link up in the stories themselves. Here are a few simple snippets to show where I'm at right now:
Component:
import React from 'react'
import styles from './VideoEntryTile.module.scss'
const VideoEntryTile: React.FC = () => {
return (
// This displays properly in the browser but not storybook
<div className={styles.container}>
<p>Hello</p>
</div>
)
}
export default VideoEntryTile
SCSS module:
.container {
background-color: blueviolet;
}
Component story:
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import VideoEntryTile from './VideoEntryTile';
export default {
title: 'Video Entry Tile',
component: VideoEntryTile,
argTypes: {
},
} as ComponentMeta<typeof VideoEntryTile>;
const Template: ComponentStory<typeof VideoEntryTile> = (args) => <VideoEntryTile {...args} />;
export const Primary = Template.bind({});
Primary.args = {};
./storybook/main.js:
module.exports = {
core: {
builder: 'webpack5',
},
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)",
"../src/components/**/*.stories.tsx"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
{
name: '#storybook/preset-scss',
options: {
sassLoaderOptions: {
modules: true
}
}
}
],
"framework": "#storybook/react"
}
Can anyone tell me where I'm going wrong? Thanks.
Create a file called scss-preset.js in .storybook:
// Copied from https://github.com/storybookjs/presets/blob/master/packages/preset-scss/index.js
function wrapLoader(loader, options) {
if (options === false) {
return [];
}
return [
{
loader,
options,
},
];
}
function webpack(webpackConfig = {}, options = {}) {
const { module = {} } = webpackConfig;
const {
styleLoaderOptions,
cssLoaderOptions,
sassLoaderOptions,
rule = {},
} = options;
return {
...webpackConfig,
module: {
...module,
rules: [
...(module.rules || []),
{
test: /(?<!module)\.s[ca]ss$/,
...rule,
use: [
...wrapLoader('style-loader', styleLoaderOptions),
...wrapLoader('css-loader', {
...cssLoaderOptions,
modules: false,
}),
...wrapLoader('sass-loader', sassLoaderOptions),
],
},
{
test: /\.module\.s[ca]ss$/,
...rule,
use: [
...wrapLoader('style-loader', styleLoaderOptions),
...wrapLoader('css-loader', {
...cssLoaderOptions,
modules: true,
}),
...wrapLoader('sass-loader', sassLoaderOptions),
],
},
],
},
};
}
module.exports = { webpack };
This is going through each scss file and, if it's a module.scss file, processing it differently than the ones that aren't.
in main.js, add presets: [path.resolve("./.storybook/scss-preset.js")], to module.exports. Should be all you need.
Make sure you have installed sass
npm install sass --save
Install scss preset
npm install #storybook/preset-scss --save
Add it to the addons list inside your .storybook/main.js
addons: ['#storybook/preset-scss',]

How to work with pdf files on back4app parse server

i have been exploring back4app parse server. i am tiered of finding documentation for pdf files. I am trying to generate some pdf files on back4app parse server from its cloud function.i do not see any documentation for it. will some one guide me to find it and little enlightment will be highly appreciated.
edit:
pdf creator library out there ask for output path but in back4app we don't have early access to path. we create new file passing it data and then save it to object. how do we achieve the order.
I some how managed get it done, thank you #Davi macedo. but there is some problem, i created pdf locally(root) cloud code,deleting it each time is envitable . this could be problem for more requests.
can someone improve little bit. would be great.
Here is my code.
I have used pdf-creator-node for creating pdf.
var pdf = require("pdf-creator-node");
var fs = require("fs")
var path = require('path')
const pdf2base64 = require('pdf-to-base64');
Parse.Cloud.define("pdf", async (req) => {
//creating pdf
// var html = fs.readFileSync("template.html", "utf8");
var html = fs.readFileSync(path.resolve(__dirname, 'template.html'), {
encoding: 'utf-8' });
var options = {
format: "A3",
orientation: "portrait",
border: "10mm",
header: {
height: "45mm",
contents: '<div style="text-align: center;">Author: Shyam Hajare</div>'
},
footer: {
height: "28mm",
contents: {
first: 'Cover page',
2: 'Second page', // Any page number is working. 1-based index
default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // fallback value
last: 'Last Page'
}
}
};
var users = [
{
name: "Shyam",
age: "26",
},
{
name: "Navjot",
age: "26",
},
{
name: "Vitthal",
age: "26",
},
];
var document = {
html: html,
data: {
users: users,
},
path: path.resolve(__dirname, './pdfs', 'my_file.pdf'),//using path is necessary
type: "",
};
return pdf
.create(document, options)
.then((res) => {
return pdf2base64(path.resolve(__dirname, "/usr/src/app/data/cloud /pdfs/my_file.pdf")) //this path /usr/src/app/data/cloud/pdfs still mystery to me.
.then(
(response) => {
// console.log(response); //cGF0aC90by9maWxlLmpwZw==
const saveFiles = async () => {
//creating file
const parseFile = new Parse.File('pdfFile.pdf',{base64:response});
// console.log(parseFile)
//saving file
const responseFile = await parseFile.save();
const Document = Parse.Object.extend('Document');
const document = new Document();
//saving it to object.
document.set('document', responseFile);
await document.save();
}
saveFiles();
}
)
.catch(
(error) => {
console.log(error); //Exepection error....
}
)
console.log(res);
})
.catch((error) => {
console.error(error);
});
});

How to emit once the observable data variable is not NULL

I'm new to RxJS, and I'm trying to figure out how to observe the data when it become available. I'm using Nuxt SSR and I'm fetching data from Firebase. The initial post value is set to null, and once the data object become available, it should run the head() function only once. I get this type error.
Cannot read property 'pipe' of null
If I initiate post: {}, as empty object, I get this type error.
post$.pipe is not a function
Appreciate if I can get some help or guidance.
// page\:post.vue
<script>
import { mapState, mapActions } from 'vuex'
import { take } from 'rxjs/operators'
export default {
fetch() {
this.fetchPost()
},
computed: {
...mapState('posts', ['post']),
},
methods: {
...mapActions('posts', ['fetchPost']),
},
head() {
const post$ = this.post
post$.pipe(take(1)).subscribe((post) => {
return {
title: this.post.title,
link: [{ rel: 'canonical', href: this.post.canonical }],
meta: [
{ hid: 'name', itemprop: 'name', content: this.post.title },
{
hid: 'description',
itemprop: 'description',
content: this.post.content,
},
],
}
})
},
}
</script>
// store\posts.js
export const state = () => ({
post: null,
})
export const mutations = {
setPost(state, payload) {
state.post = payload
},
}
export const actions = {
async fetchPost({ commit }, key) {
const doc = await postsCollection.doc(key).get()
if (doc.exists) commit('setPost', doc.dat())
},
}
Edit
Using Subject. However, there is still issue where the meta tags are generated before the post data is set.
// page\:post.vue
<script>
import { mapState, mapActions } from 'vuex'
import { take } from 'rxjs/operators'
export default {
fetch() {
this.fetchPost()
},
computed: {
...mapState('posts', ['post']),
},
methods: {
...mapActions('posts', ['fetchPost']),
},
head() {
const postSubject = new Subject()
const post = postSubject.asObservable()
postSubject.next(this.post)
post.subscribe((post) => {
return {
title: post.title,
link: [{ rel: 'canonical', href: post.canonical }],
meta: [
{ hid: 'name', itemprop: 'name', content: post.title },
{
hid: 'description',
itemprop: 'description',
content: post.content,
},
],
}
})
},
}
</script>
// store\posts.js
export const state = () => ({
post: null,
})
export const mutations = {
setPost(state, payload) {
state.post = payload
},
}
export const actions = {
async fetchPost({ commit }, key) {
const doc = await postsCollection.doc(key).get()
if (doc.exists) commit('setPost', doc.dat())
},
}
You need to subscribe to an Observable. As I understood, in your case this.post is not type of an Observable.
As this.post is populated at some point of time, you need to subscribe to an observable which should emit data when you say this.post is now populated with data. For that you can use a Subject.
See this example: link

Next.js: Importing images into a component

I am using "next": "^9.4.4",
And have : "next-images": "^1.4.0", "next-optimized-images": "^2.6.1",
And this is my next-config.js
const withCSS = require('#zeit/next-css');
const withSass = require('#zeit/next-sass');
const withImages = require('next-images');
const optimizedImages = require('next-optimized-images');
module.exports = withImages(
optimizedImages(
withCSS(
withSass({
target: 'serverless',
env: {
MAPBOX_ACCESS_TOKEN:
'TK421'
},
webpack(config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000,
target: 'serverless'
}
}
});
return config;
}
})
)
)
);
But in my component I get a broken image link, This is my component:
import { useState, useEffect } from 'react';
import { Card, Icon, Image, Segment, Form } from 'semantic-ui-react';
import axios from 'axios';
function ImageUploader({ userAvatar }) {
var [userAvatar, setUserAvatar] = useState(userAvatar);
useEffect(() => {
setUserAvatar(userAvatar);
}, [userAvatar]);
function fileUploader(e) {
console.log('event fileUploader ', e);
var imageFormObj = new FormData();
console.log('e.target.files[0] ', e.target.files[0]);
imageFormObj.append('imageName', 'multer-image-' + Date.now());
imageFormObj.append('imageData', e.target.files[0]);
setUserAvatar(window.URL.createObjectURL(e.target.files[0]));
console.log('userAvatar ', userAvatar);
console.log('imageFormObj ', imageFormObj);
axios
.post(`/users/uploadmulter`, imageFormObj)
.then(data => {
if (data.data.success) {
alert('Image has been successfully uploaded using multer');
}
})
.catch(err => {
alert('Error while uploading image using multer');
});
}
return (
<>
<Image
src={require('../../public/static/uploads/profile-avatars/placeholder.jpg')}
alt="user-avatar"
/>
</>
);
}
I am confused too because in the docs it seems to indicate static files/images are supported outta the box...
Static File Serving
Next.js can serve static files, like images, under a folder called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/).
For example, if you add an image to public/my-image.png, the following code will access the image:
I've tried as they recommend:
<Image src="/uploads/profile-avatars/placeholder.jpg" alt="user-avatar" />
And the funny thing I am not getting a 404 in the browser!
Any help would be appreciated!
Your image must be located in a folder with the reserved name public. Then the offer from the box will work.

GraphQL Error: Field "image" must not have a selection since type "String" has no subfields

Description
I am new and learning GatsbyJs/NetlifyCM/GraphQL so I am sorry if I come across as stupid. I, however, have been trying to fix this issue for 5 days now and am at my wits end. I am trying to implement NetlifyCMS into a GatsbyJS blog starter. I seem to have everything working except the images. Graphql is querying the image URL as "img/image.jpg" which is what is set in the MD file, and that works fine. However, when I make a new post with NetlifyCMS it sets the URL to "/img/image.jpg" and graphql can't seem to find it. The extra slash in front causes the error. If I remove the slash OR put a dot in front of the slash, "./img/image.jpg" it builds fine which leads me to believe relative paths may be working? I have read issue 4123, issue 13938, issue 5990, As well as multiple other similar issues and blogs and still, am unable to get it to work. I have tried implementing gatsby-remark-relative-images and gatsby-plugin-netlify-cms but neither seems to fix it.
Any help would be appreciated. I am newer at web development and very new to gatsby/graphql. Thank you.
Steps to reproduce
remove or add a / in front of the image URL in the MD file. Here is a link to the repo to clone/download https://github.com/AaronCuddeback/blog.aaroncuddeback.com
Environment
System:
OS: Windows 10
CPU: (16) x64 Intel(R) Core(TM) i9-9900KF CPU # 3.60GHz
Binaries:
Node: 12.13.0 - C:\Program Files\nodejs\node.EXE
npm: 6.13.0 - ~\AppData\Roaming\npm\npm.CMD
Languages:
Python: 2.7.17 - /usr/bin/python
Browsers:
Edge: 44.18362.387.0
npmPackages:
gatsby: 2.18.2 => 2.18.2
gatsby-image: 2.2.33 => 2.2.33
gatsby-plugin-canonical-urls: 2.1.15 => 2.1.15
gatsby-plugin-emotion: 4.1.15 => 4.1.15
gatsby-plugin-feed: 2.3.21 => 2.3.21
gatsby-plugin-google-analytics: 2.1.28 => 2.1.28
gatsby-plugin-netlify: ^2.1.25 => 2.1.25
gatsby-plugin-netlify-cms: ^4.1.28 => 4.1.28
gatsby-plugin-netlify-cms-paths: ^1.3.0 => 1.3.0
gatsby-plugin-postcss: 2.1.15 => 2.1.15
gatsby-plugin-react-helmet: 3.1.15 => 3.1.15
gatsby-plugin-sharp: 2.3.2 => 2.3.2
gatsby-plugin-sitemap: 2.2.21 => 2.2.21
gatsby-plugin-typescript: 2.1.19 => 2.1.19
gatsby-remark-abbr: 2.0.0 => 2.0.0
gatsby-remark-copy-linked-files: 2.1.30 => 2.1.30
gatsby-remark-images: 3.1.33 => 3.1.33
gatsby-remark-prismjs: 3.3.24 => 3.3.24
gatsby-remark-relative-images: ^0.2.3 => 0.2.3
gatsby-remark-responsive-iframe: 2.2.27 => 2.2.27
gatsby-remark-smartypants: 2.1.16 => 2.1.16
gatsby-source-filesystem: 2.1.38 => 2.1.38
gatsby-transformer-json: 2.2.19 => 2.2.19
gatsby-transformer-remark: 2.6.37 => 2.6.37
gatsby-transformer-sharp: 2.3.5 => 2.3.5
gatsby-transformer-yaml: 2.2.17 => 2.2.17
File Contents:
gatsby-config.js:
const path = require('path');
module.exports = {
siteMetadata: {
title: 'Ghost',
description: 'The professional publishing platform',
siteUrl: 'https://gatsby-casper.netlify.com', // full path to blog - no ending slash
},
mapping: {
'MarkdownRemark.frontmatter.author': 'AuthorYaml',
},
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/static/img`,
name: 'uploads',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/content`,
name: 'content',
},
},
{
resolve: `gatsby-plugin-netlify-cms-paths`,
options: {
cmsConfig: `/static/admin/config.yml`,
},
},
'gatsby-plugin-sitemap',
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
`gatsby-plugin-netlify-cms-paths`,
{
resolve: `gatsby-remark-relative-images`,
options: {
name: 'uploads', // Must match the source name ^
},
},
{
resolve: 'gatsby-remark-images',
options: {
maxWidth: 1170,
quality: 90,
},
},
{
resolve: 'gatsby-remark-responsive-iframe',
options: {
wrapperStyle: 'margin-bottom: 1rem',
},
},
'gatsby-remark-prismjs',
'gatsby-remark-copy-linked-files',
'gatsby-remark-smartypants',
'gatsby-remark-abbr',
],
},
},
'gatsby-transformer-json',
{
resolve: 'gatsby-plugin-canonical-urls',
options: {
siteUrl: 'https://gatsby-casper.netlify.com',
},
},
'gatsby-plugin-emotion',
'gatsby-plugin-typescript',
'gatsby-transformer-sharp',
'gatsby-plugin-react-helmet',
'gatsby-transformer-yaml',
'gatsby-plugin-feed',
{
resolve: 'gatsby-plugin-postcss',
options: {
postCssPlugins: [require('postcss-color-function'), require('cssnano')()],
},
},
{
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: 'UA-XXXX-Y',
// Puts tracking script in the head instead of the body
head: true,
// IP anonymization for GDPR compliance
anonymize: true,
// Disable analytics for users with `Do Not Track` enabled
respectDNT: true,
// Avoids sending pageview hits from custom paths
exclude: ['/preview/**'],
// Specifies what percentage of users should be tracked
sampleRate: 100,
// Determines how often site speed tracking beacons will be sent
siteSpeedSampleRate: 10,
},
},
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-netlify`,
],
};
gatsby-node.js
const path = require('path');
const _ = require('lodash');
const { fmImagesToRelative } = require('gatsby-remark-relative-images');
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node);
// Sometimes, optional fields tend to get not picked up by the GraphQL
// interpreter if not a single content uses it. Therefore, we're putting them
// through `createNodeField` so that the fields still exist and GraphQL won't
// trip up. An empty string is still required in replacement to `null`.
switch (node.internal.type) {
case 'MarkdownRemark': {
const { permalink, layout, primaryTag } = node.frontmatter;
const { relativePath } = getNode(node.parent);
let slug = permalink;
if (!slug) {
slug = `/${relativePath.replace('.md', '')}/`;
}
// Used to generate URL to view this content.
createNodeField({
node,
name: 'slug',
value: slug || '',
});
// Used to determine a page layout.
createNodeField({
node,
name: 'layout',
value: layout || '',
});
createNodeField({
node,
name: 'primaryTag',
value: primaryTag || '',
});
}
}
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
allMarkdownRemark(
limit: 2000
sort: { fields: [frontmatter___date], order: ASC }
filter: { frontmatter: { draft: { ne: true } } }
) {
edges {
node {
excerpt
timeToRead
frontmatter {
title
tags
date
draft
image {
childImageSharp {
fluid(maxWidth: 3720) {
aspectRatio
base64
sizes
src
srcSet
}
}
publicURL
}
author {
id
bio
avatar {
children {
... on ImageSharp {
fixed(quality: 90) {
src
}
}
}
}
}
}
fields {
layout
slug
}
}
}
}
allAuthorYaml {
edges {
node {
id
}
}
}
}
`);
if (result.errors) {
console.error(result.errors);
throw new Error(result.errors);
}
// Create post pages
const posts = result.data.allMarkdownRemark.edges;
// Create paginated index
const postsPerPage = 6;
const numPages = Math.ceil(posts.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/' : `/${i + 1}`,
component: path.resolve('./src/templates/index.tsx'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
});
});
posts.forEach(({ node }, index) => {
const { slug, layout } = node.fields;
const prev = index === 0 ? null : posts[index - 1].node;
const next = index === posts.length - 1 ? null : posts[index + 1].node;
createPage({
path: slug,
// This will automatically resolve the template to a corresponding
// `layout` frontmatter in the Markdown.
//
// Feel free to set any `layout` as you'd like in the frontmatter, as
// long as the corresponding template file exists in src/templates.
// If no template is set, it will fall back to the default `post`
// template.
//
// Note that the template has to exist first, or else the build will fail.
component: path.resolve(`./src/templates/${layout || 'post'}.tsx`),
context: {
// Data passed to context is available in page queries as GraphQL variables.
slug,
prev,
next,
primaryTag: node.frontmatter.tags ? node.frontmatter.tags[0] : '',
},
});
});
// Create tag pages
const tagTemplate = path.resolve('./src/templates/tags.tsx');
const tags = _.uniq(
_.flatten(
result.data.allMarkdownRemark.edges.map(edge => {
return _.castArray(_.get(edge, 'node.frontmatter.tags', []));
}),
),
);
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag,
},
});
});
// Create author pages
const authorTemplate = path.resolve('./src/templates/author.tsx');
result.data.allAuthorYaml.edges.forEach(edge => {
createPage({
path: `/author/${_.kebabCase(edge.node.id)}/`,
component: authorTemplate,
context: {
author: edge.node.id,
},
});
});
};
exports.onCreateWebpackConfig = ({ stage, actions }) => {
// adds sourcemaps for tsx in dev mode
if (stage === `develop` || stage === `develop-html`) {
actions.setWebpackConfig({
devtool: 'eval-source-map',
});
}
};

Resources