Generate static pages from json data - sveltekit

(This is just for my understanding)
If I have json file that contains array of data is it possible to generate html page for each entry/object of the array?
The json file is static and it is located under the src folder:
//+page.js
import snippets from '../../../data/product_data.json';
export async function load({ params }) {
const { slug } = params;
return snippets.filter((s) => s.name == slug)[0];
}
//+page.svelte
<script>
export const prerender = true;
export let data;
</script>
<div>{data.name}</div>
//svelte.config.js
import adapter from '#sveltejs/adapter-static';
/** #type {import('#sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({ precompress: true, fallback: 'index.html' })
}
};
export default config;

Related

Next.js keeps generating 404 page instead of blog details page

I am trying to generate my blog details page but next keeps giving me the 404 page. I am fetching the data from a headless cms called contentful. Here is what my file structure and code looks like
And this is my [path]s.js page
import {createClient} from "contentful"
const client = createClient({
space: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
accessToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
});
export const getStaticPaths = async () => {
const res = await client.getEntries({
content_type: "blog"
})
const paths = res.items.map(item => {
return {
params: {slug: item.fields.slug}
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async ({params}) => {
const {items} = await client.getEntries({
content_type: "blog",
"fields.slug": params.slug
})
return {
props: {blog: items[0]}
}
}
function blogDetails({blog}) {
console.log(blog)
return (
<div>
Blog details
</div>
)
}
export default blogDetails
I do not understand where I am going wrong. What could be my problem?.
Firstly, you cannot use the square brackets as a "wildcard" at the beginning of the path and add some other characters after. So you should rename your file as just [path].js (without the s after [path]).
Secondly, in your getStaticPaths function, when you return the object of params inside paths, name the keys as the dynamic path you've named your file as. In your case, the object you return should be params: {path: item.fields.slug}. Alternatively, you can rename your file as [slug].js and leave the code like it is.
// [path].js
export const getStaticPaths = async () => {
const res = await client.getEntries({
content_type: "blog"
})
const paths = res.items.map(item => {
return {
params: {path: item.fields.slug} //renamed the key as "path"
}
})
return {
paths,
fallback: false
}
}
You can read more about dynamic routes and getStaticPaths() in the official documentation.

Filter for PDF files while exploring with Angular and Capacitor Filesystem

I use the following code to explore the file system in an ionic App the save files to the device for uploading latter.
import { Component, ElementRef, ViewChild } from '#angular/core';
import { Filesystem, Directory } from '#capacitor/filesystem';
import write_blob from 'capacitor-blob-writer';
export class QuestionPhotosPage {
#ViewChild('filepicker') uploader: ElementRef;
addFile() {
this.uploader.nativeElement.click();
console.log("click open file explorer");
}
async fileSelected($event) {
console.log("$event", $event)
const selected = $event.target.files[0];
let fileName = this.createFileNameAlt(selected.name)
await write_blob({
directory: Directory.Documents,
path: `${this.FILE_DIR}/${fileName}`,
blob: selected,
fast_mode: true,
recursive: true,
on_fallback(error) { console.error(error) }
})
}
I was wondering if there is away to filter the addFile() to only show folders and pdf documents?
Using the plugin github.com/capawesome-team/capacitor-file-picker here is what my code looks like.
import { Filesystem, Directory } from '#capacitor/filesystem';
import write_blob from 'capacitor-blob-writer';
import { File, FilePicker } from '#capawesome/capacitor-file-picker';
async pickFile(): Promise<void> {
const types = ["application/pdf"];
const multiple = false;
const readData = true;
const { files } = await FilePicker.pickFiles({ types, multiple, readData });
this.fileSelected(files);
}
async fileSelected(file: any) {
let fileName = this.createFileNameAlt(file[0].name)
await write_blob({
directory: Directory.Documents,
path: `${this.FILE_DIR}/${fileName}`,
blob: file,
fast_mode: true,
recursive: true,
on_fallback(error) { console.error(error) }
})
}

how to replace strings with .env (dotenv-flow) with svelte-kit?

Inside svelte.config.js I have this:
preprocess: autoPreprocess({
replace: [
['API_ENDPOINT', JSON.stringify(process.env.API_ENDPOINT)]
]
}),
It should replace the string "API_ENDPOINT" but it isn't.
Here is how I'm using it:
async function api(url: string, body = {}, opts = {}) {
const endpoint = 'API_ENDPOINT';
console.log(endpoint);
const res = await fetch(endpoint + url, {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(body)
});
if (!res.ok) {
const err = await res.json();
throw (err || res.statusText);
}
return opts.raw ? await res.text() : await res.json();
}
export default api;
All i get is http://localhost:3000/API_ENDPOINT/subscriptions
in your svelte.config.js file, instead of
autoPreprocess({
replace: [['process.env.NODE_ENV', JSON.stringify(process.env.NODE_ENV)]],
});
use
import sveltePreprocess from 'svelte-preprocess'
sveltePreprocess({
replace: [['process.env.NODE_ENV', JSON.stringify(process.env.NODE_ENV)]],
});
It seems to me that autoPreprocess was deprecated but the README docs were not correctly updated.
You also may want to consider doing one of the following:
use process.env.API_ENDPOINT directly
const endpoint = process.env.API_ENDPOINT;
create a separate javascript function to handle environment variables
something like this
envVars.js
import dotenv from 'dotenv-flow';
import path from 'path';
dotenv.config({
path: path.resolve(__dirname, `path-to-your-(.env)-file`),
});
export const getEnvVar = (key) => process.env[key];
and use it like this:
import { getEnvVar } from 'path-to-envVars'
const endpoint = getEnvVar('API_ENDPOINT');
if you're already using Vite as your build tool
Add the variables in a .env file like so:
VITE_YOUR_ENV_VAR_GOES_HERE=bar
in your case:
VITE_API_ENDPOINT=your_value
then import them in a separate javascript or typescript file
envVars.js
export const envVars = {
API_ENDPOINT: import.meta.env.VITE_API_ENDPOINT
};
and then import envVars and use it as so:
import { envVars } from 'path-to-envVars.js'
and now you can read your environment variable like this:
envVars.myVariable
or, in your case
const endpoint = envVars.API_ENDPOINT

getting error only with vercel deployment when displaying props (works locally)

I am building a headless wordpress CMS(and using next.js template) .
I was able to successfully deploy to the vercel servers, I then started to add some content, in this case the Wordpress title field.. and this works great on my local server and display the title properly with no errors.. but when I update my git it kills the vercel deployment and I get this error.. I also get this error if I try to use content or dangerouslySetHTML
to clarify, if I remove this, it deploys fine
{ page.title }
Unhandled error during request: TypeError: Cannot read property 'title' of undefined
--
17:01:15.529 | at Page (/vercel/workpath0/.next/serverless/pages/[slug].js:1634:412)
this is my page code [slug].js
import Head from 'next/head'
import Link from 'next/link'
import Container from '../components/container'
import MoreStories from '../components/more-stories'
import HeroPost from '../components/hero-post'
import Intro from '../components/intro'
import Layout from '../components/layout'
import { getAllPagesWithSlug, getAllPagesBySlug } from '../lib/api'
import { CMS_NAME } from '../lib/constants'
import Header from '../components/header'
export default function Page( {page} ) {
return (
<>
<Layout>
<Head>
<title></title>
</Head>
<Header />
<Container>
{ page.title }
</Container>
</Layout>
</>
)
}
export async function getStaticProps({ params }) {
const data = await getAllPagesBySlug(params.slug)
console.log(data)
return {
props: {
page: data.page,
},
}
}
export async function getStaticPaths() {
const allPages = await getAllPagesWithSlug()
return {
//paths: [ { params: { slug: '${node.uri}' } } ],
paths: allPages.edges.map(({ node }) => `/${node.slug}`) || [],
fallback: true,
}
}
this is my query lib/api
const API_URL = process.env.WORDPRESS_API_URL
async function fetchAPI(query, { variables } = {}) {
const headers = { 'Content-Type': 'application/json' }
if (process.env.WORDPRESS_AUTH_REFRESH_TOKEN) {
headers[
'Authorization'
] = `Bearer ${process.env.WORDPRESS_AUTH_REFRESH_TOKEN}`
}
const res = await fetch(API_URL, {
method: 'POST',
headers,
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
return json.data
}
export async function getAllPagesBySlug($id) {
const data = await fetchAPI(`
{
page(id: "${$id}", idType: URI) {
uri
slug
content
title
featuredImage {
node {
sourceUrl
}
}
}
}
`)
return data
}

Next.js | Is there any way to render an .xml file?

I'm trying to render an XML file when pointing to www.example.com/sitemap.xml. Since the project was developed using Next.js, the routing works with the js files stored in the pages directory:
example.com/help -> help.js
example.com/info -> info.js
So, is there any way to achieve this by avoiding accessing the backend?
JS Version
/pages/sitemap.xml.jsx
import React from 'react'
class Sitemap extends React.Component {
static async getInitialProps({ res }) {
res.setHeader('Content-Type', 'text/xml')
res.write(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
...
</urlset>`)
res.end()
}
}
export default Sitemap
TS Version
/pages/sitemap.xml.tsx
import { GetServerSideProps } from 'next'
import React from 'react'
const Sitemap: React.FC = () => null
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
if (res) {
res.setHeader('Content-Type', 'text/xml')
res.write(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>`)
res.end()
}
return {
props: {},
}
}
export default Sitemap
API Version
/pages/api/sitemap.xml.tsx
import type { NextApiRequest, NextApiResponse } from 'next'
import { getAllContentPagesQuery, getAllShopProductsQuery } from './utils/requests'
export default async (req: NextApiRequest, res: NextApiResponse<string>) => {
const pages = await getAllContentPagesQuery()
const products = await getAllShopProductsQuery()
const frontUrl = "https://localhost:3000"
const pagesAndSlugs = [...pages, ...products].map(url => ({
url: `${frontUrl}${'Variations' in url ? '/product/' : '/'}${url.slug}`, // -> /page1, /product/myproduct...
updatedAt: url.updatedAt,
}))
const urls = pagesAndSlugs.map(
({ url, updatedAt }) => `
<url>
<loc>${url}</loc>
<lastmod>${new Date(updatedAt).toISOString()}</lastmod>
</url>
`
)
.join('')
res
.setHeader('Content-Type', 'text/xml')
.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
.status(200)
.send(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`)
}
:D
Add a static file called sitemap.xml under public directory
public/sitemap.xml
after build you can access www.yourdomain.com/sitemap.xml
Read more on static files: https://nextjs.org/docs/basic-features/static-file-serving
You can use nextjs api routes.
path - pages/api/sitemap.ts
content -
import type { NextApiRequest, NextApiResponse } from 'next';
import { SitemapStream, streamToPromise } from 'sitemap';
async function sitemap(req: NextApiRequest, res: NextApiResponse<string>) {
try {
const smStream = new SitemapStream({
hostname: `https://${req.headers.host}`,
});
// List of posts
const posts: string[] = ['hello'];
// Create each URL row
posts.forEach(() => {
smStream.write({
url: `/post/hello`,
changefreq: 'daily',
priority: 0.9,
});
});
// End sitemap stream
smStream.end();
// XML sitemap string
const sitemapOutput = (await streamToPromise(smStream)).toString();
res.writeHead(200, {
'Content-Type': 'application/xml',
});
// Display output to user
res.end(sitemapOutput);
} catch (e) {
console.log(e);
res.send(JSON.stringify(e));
}
}
export default sitemap;
In robots.txt you can specify
Sitemap: https://[SITE_DOMAIN_HERE]/api/sitemap

Resources