Embed current build time into humans.txt file with nextjs - next.js

Placing a humans.txt file into nextjs' /public folder works fine for a static file.
However I'd like to annotate the file with the date of the latest page build (when next build is called). So I created pages/humans.txt.tsx which renders a string that also contains the build time static date:
export default function Test({ buildTime }) {
return `Hello ${buildTime}`
}
export async function getStaticProps() {
return {
props: {
buildTime: new Date().toISOString()
}
}
}
I tried to customize pages/_document.js but even with everything stripped down (for testing) it still renders the doctype and one div with my text in it.
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
ctx.renderPage = (props) => {
return {
html: "text",
head: null,
}
}
// Run the parent `getInitialProps`, it now includes the custom `renderPage`
const initialProps = await Document.getInitialProps(ctx)
return initialProps
}
render() {
return <Main/>
}
}
Output:
<!DOCTYPE html><div id="__next">text</div>
Returning just string from my documents render instead of <Main/> still renders the doctype and also causes a warning, since render should return an Element.
So I am out of ideas and might resort to using a prebuild script in package.json prebuild: sed ./pages/humans.txt... to replace a marker in the file with the system date and pipe it to public/humans.txt.

Here is an interesting runtime alternative:
Rewriting /humans.txt to /api/humans
You can use the following rule:
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/humans.txt',
destination: '/api/humans',
},
]
},
}
Check the Rewrites docs here
Writing /api/humans
Now you can use any response in your API. However, make sure you are caching it:
// /pages/api/humans.js
export default function handler(req, res) {
res.setHeader('Cache-control', 's-maxage=6000, stale-while-revalidate=30')
res.setHeader('Content-type', 'text/plain')
res.status(200).end('example')
}
Check the API routes docs here

Related

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!

Adding prefix to Nextjs dynamic route

I have quite a lot of routes defined and one of the routes is dedicated to user profiles.
Each user has a public profile accessible from HTTP://example.com/#username.
I have tried creating file pages/#[username].js but it doesn't seem to work.
Is there a way to have this behavior without passing # sign with the username because this would greatly complicate index.js handling homepage and I would like to have that code separated.
You can now do this like so in next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/#:username',
destination: '/users/:username'
}
]
}
}
This will make any link to /#username go to the /users/[username] file, even though the address bar will show /#username.
Then, in your /pages/[username].tsx file:
import { useRouter } from 'next/router'
export default function UserPage() {
const { query = {} } = useRouter()
return <div>User name is {query.username || 'missing'}</div>
}
Next.js does not support this yet.
You should watch this issue.

Next.js returns 500: internal server error in Production

Created a next.js full stack application. After production build when I run next start it returns 500 : internal server. I'm using environment varibles for hitting api.
env.development file
BASE_URL=http://localhost:3000
It was working fine in development
service.ts
import axios from 'axios';
const axiosDefaultConfig = {
baseURL: process.env.BASE_URL, // is this line reason for error?
headers: {
'Access-Control-Allow-Origin': '*'
}
};
const axio = axios.create(axiosDefaultConfig);
export class Steam {
static getGames = async () => {
return await axio.get('/api/getAppList');
};
}
Do you have a next.config.js file?
To add runtime configuration to your app open next.config.js and add the publicRuntimeConfig and serverRuntimeConfig configs:
module.exports = {
serverRuntimeConfig: {
// Will only be available on the server side
mySecret: 'secret',
secondSecret: process.env.SECOND_SECRET, // Pass through env variables
},
publicRuntimeConfig: {
// Will be available on both server and client
staticFolder: '/static',
},
}
To get access to the runtime configs in your app use next/config, like so:
import getConfig from 'next/config'
// Only holds serverRuntimeConfig and publicRuntimeConfig
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
// Will only be available on the server-side
console.log(serverRuntimeConfig.mySecret)
// Will be available on both server-side and client-side
console.log(publicRuntimeConfig.staticFolder)
function MyImage() {
return (
<div>
<img src={`${publicRuntimeConfig.staticFolder}/logo.png`} alt="logo" />
</div>
)
}
export default MyImage
I hope this helps.
I dont think you have setup env.
You need to configure it for it to work. Try it without it and it should work fine!

How to load a file into an Angular Test?

i'm trying to test an extension validator using Karma and Jasmine with latest Angular version. I have a service that receives a File and checks its extension and MIME type, this is the method signature:
public validateFileType(file: File, validTypes: string[]): Observable<boolean>
As you can see the method receives a File, so i want to either load some files i have in a /testing folder or create a file with '.doc'/'.xls'/'.pdf' extension (i would like to use the first approach, already tried creating a .doc, .xls file and the signature numbers does not match any real file).
But its impossible to load any File as the HttpClient can not be instantiated in a Karma test, i have tried everything i know and searched a lot, any help is really appreciated.
Example
import { TestBed } from '#angular/core/testing';
import { FileValidatorService } from './file-validator.service';
describe('FileValidatorService', () => {
let service: FileValidatorService;
beforeEach(() => {
TestBed.configureTestingModule({ providers: [FileValidatorService] });
service = TestBed.get(FileValidatorService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('Types', () => {
it('should be false', () => {
const content = 'Hello test';
const data = new Blob([content], { type: 'application/zip' });
const arrayOfBlob = new Array<Blob>();
arrayOfBlob.push(data);
const applicationZip = new File(arrayOfBlob, 'Mock.zip', { type: 'application/zip' });
service.validateFileType(applicationZip, ['.doc', '.xls']).subscribe((result: boolean) => {
expect(result).toBeFalsy();
});
});
});
});
This test only works because the file i created generates some random signature code that i don't use so its not recognized and it doesn't match the expected types so is not working as it should.

Resources