Unverified Commit 3429d397 authored by Sébastien Chopin's avatar Sébastien Chopin Committed by GitHub

chore: refactor to nuxt 4 compatibility (#148)

* chore: refactor to nuxt 4 compatibility * chore: remove hub at the moment (no needed) * Update pnpm-lock.yaml * chore: add caching with hub * chore: update links
parent 9f592c04
{
"extends": "@nuxt",
"rules": {
"vue/no-v-html": "off",
"vue/no-multiple-template-root": "off"
}
}
...@@ -13,3 +13,5 @@ package-lock.json ...@@ -13,3 +13,5 @@ package-lock.json
public/manifest*.json public/manifest*.json
public/sw.js public/sw.js
public/workbox-sw*.js* public/workbox-sw*.js*
.data
\ No newline at end of file
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-present, Yuxi (Evan) You Copyright (c) 2013-present, Yuxi (Evan) You & Nuxt core team
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
Hacker News clone built with [Nuxt](https://nuxt.com). Hacker News clone built with [Nuxt](https://nuxt.com).
<p align="center"> <p align="center">
<a href="https://hn.nuxt.space" target="_blank"> <a href="https://hn.nuxt.dev" target="_blank">
<img width="1090" src="https://hn.nuxt.space/cover.jpg"> <img width="1090" src="https://hn.nuxt.dev/cover.jpg">
<br> <br>
Live Demo Live Demo
</a> </a>
...@@ -12,15 +12,13 @@ Hacker News clone built with [Nuxt](https://nuxt.com). ...@@ -12,15 +12,13 @@ Hacker News clone built with [Nuxt](https://nuxt.com).
## Demo ## Demo
https://hn.nuxt.space https://hn.nuxt.dev
> Hosted on [Vercel](https://vercel.com/): `npm run build` > Hosted on Cloudflare Pages with [NuxtHub](https://hub.nuxt.com): `npm run build`
To disable server-side render for a page, simply append `?csr` to the URL, example: https://hn.nuxt.space/news/1?csr
## Performance ## Performance
- Lighthouse [100/100](https://pagespeed.web.dev/report?url=https%3A%2F%2Fhackernews-git-nuxt3-nuxt-js.vercel.app%2Fnews%2F1) (Slow 4G / Mobile Moto G4) - Lighthouse [100/100](https://pagespeed.web.dev/report?url=https%3A%2F%2Fhn.nuxt.dev%2Fnews%2F1) (Slow 4G / Mobile Moto G4)
- Interactive: 1.4s - Interactive: 1.4s
- Total Blocking Time: 30ms - Total Blocking Time: 30ms
...@@ -28,7 +26,7 @@ To disable server-side render for a page, simply append `?csr` to the URL, examp ...@@ -28,7 +26,7 @@ To disable server-side render for a page, simply append `?csr` to the URL, examp
- Server Side Rendering - Server Side Rendering
- Vite-based hot module replacement (HMR) dev environment - Vite-based hot module replacement (HMR) dev environment
- Deploys anywhere with zero config (Vercel, Netlify, Cloudflare, etc.) powered by [Nitro](https://github.com/unjs/nitro) - Deploys anywhere with zero config (Vercel, Netlify, Cloudflare, etc.) powered by [Nitro](https://nitro.unjs.io)
- Code Splitting - Code Splitting
- Prefetch/Preload JS + DNS + Data - Prefetch/Preload JS + DNS + Data
......
...@@ -11,8 +11,8 @@ useSeoMeta({ ...@@ -11,8 +11,8 @@ useSeoMeta({
}) })
useHead({ useHead({
link: [ link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' } { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' },
] ],
}) })
</script> </script>
......
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ const props = defineProps<{
feed: string, feed: string
page: number, page: number
maxPage: number maxPage: number
}>() }>()
......
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ defineProps<{
loading: boolean, loading: boolean
}>() }>()
</script> </script>
......
<script setup lang="ts"> <script setup lang="ts">
import { timeAgo } from '~/composables/utils'
defineProps({ defineProps({
comment: { comment: {
type: Object, type: Object,
required: true required: true,
} },
}) })
const open = ref(true) const open = ref(true)
function pluralize (n: number) { function pluralize(n: number) {
return n + (n === 1 ? ' reply' : ' replies') return n + (n === 1 ? ' reply' : ' replies')
} }
</script> </script>
......
<script setup lang="ts"> <script setup lang="ts">
import { timeAgo, isAbsolute, host } from '~/composables/utils' import type { Item } from '~~/types'
defineProps<{ defineProps<{
item: any item: Item
}>() }>()
</script> </script>
......
import { WritableComputedOptions } from 'vue' import type { WritableComputedOptions } from 'vue'
import { Item, User } from '~/types' import { validFeeds } from '~~/utils/api'
import type { Item, User } from '~~/types'
export interface StoreState { export interface StoreState {
items: Record<number, Item> items: Record<number, Item>
...@@ -12,15 +13,15 @@ export const useStore = () => useState<StoreState>('store', () => ({ ...@@ -12,15 +13,15 @@ export const useStore = () => useState<StoreState>('store', () => ({
items: {}, items: {},
users: {}, users: {},
comments: {}, comments: {},
feeds: Object.fromEntries(validFeeds.map(i => [i, {}])) feeds: Object.fromEntries(validFeeds.map(i => [i, {}])),
})) }))
interface FeedQuery { interface FeedQuery {
feed: string; feed: string
page: number page: number
} }
export function getFeed (state:StoreState, { feed, page }: FeedQuery) { export function getFeed(state: StoreState, { feed, page }: FeedQuery) {
const ids = state.feeds?.[feed]?.[page] const ids = state.feeds?.[feed]?.[page]
if (ids?.length) { if (ids?.length) {
return ids.map(i => state.items[i]) return ids.map(i => state.items[i])
...@@ -28,7 +29,7 @@ export function getFeed (state:StoreState, { feed, page }: FeedQuery) { ...@@ -28,7 +29,7 @@ export function getFeed (state:StoreState, { feed, page }: FeedQuery) {
return undefined return undefined
} }
export function fetchFeed (query: FeedQuery) { export function fetchFeed(query: FeedQuery) {
const state = useStore() const state = useStore()
const { feed, page } = query const { feed, page } = query
...@@ -43,43 +44,44 @@ export function fetchFeed (query: FeedQuery) { ...@@ -43,43 +44,44 @@ export function fetchFeed (query: FeedQuery) {
.forEach((item) => { .forEach((item) => {
if (state.value.items[item.id]) { if (state.value.items[item.id]) {
Object.assign(state.value.items[item.id], item) Object.assign(state.value.items[item.id], item)
} else { }
else {
state.value.items[item.id] = item state.value.items[item.id] = item
} }
}) })
}, },
() => $fetch('/api/hn/feeds', { params: { feed, page } }), () => $fetch('/api/hn/feeds', { params: { feed, page } }),
(state.value.feeds[feed][page] || []).map(id => state.value.items[id]) (state.value.feeds[feed][page] || []).map(id => state.value.items[id]),
) )
} }
export function fetchItem (id: number) { export function fetchItem(id: number) {
const state = useStore() const state = useStore()
return reactiveLoad<Item>( return reactiveLoad<Item>(
() => state.value.items[id], () => state.value.items[id],
(item) => { state.value.items[id] = item }, (item) => { state.value.items[id] = item },
() => $fetch('/api/hn/item', { params: { id } }) () => $fetch('/api/hn/item', { params: { id } }),
) )
} }
export function fetchComments (id: number) { export function fetchComments(id: number) {
const state = useStore() const state = useStore()
return reactiveLoad<Item[]>( return reactiveLoad<Item[]>(
() => state.value.comments[id], () => state.value.comments[id],
(comments) => { state.value.comments[id] = comments }, (comments) => { state.value.comments[id] = comments },
() => $fetch('/api/hn/item', { params: { id } }).then(i => i.comments!) () => $fetch('/api/hn/item', { params: { id } }).then(i => i.comments!),
) )
} }
export function fetchUser (id: string) { export function fetchUser(id: string) {
const state = useStore() const state = useStore()
return reactiveLoad<User>( return reactiveLoad<User>(
() => state.value.users[id], () => state.value.users[id],
(user) => { state.value.users[id] = user }, (user) => { state.value.users[id] = user },
() => $fetch('/api/hn/user', { params: { id } }) () => $fetch('/api/hn/user', { params: { id } }),
) )
} }
...@@ -88,15 +90,15 @@ export function fetchUser (id: string) { ...@@ -88,15 +90,15 @@ export function fetchUser (id: string) {
* *
* On server side the data will be fetched eagerly * On server side the data will be fetched eagerly
*/ */
export async function reactiveLoad<T> ( export async function reactiveLoad<T>(
get: () => T | undefined, get: () => T | undefined,
set: (data: T) => void, set: (data: T) => void,
fetch: ()=> Promise<T>, fetch: () => Promise<T>,
init?: T init?: T,
) { ) {
const data = computed({ const data = computed({
get, get,
set set,
} as WritableComputedOptions<T | undefined>) } as WritableComputedOptions<T | undefined>)
const loading = ref(false) const loading = ref(false)
...@@ -111,27 +113,30 @@ export async function reactiveLoad<T> ( ...@@ -111,27 +113,30 @@ export async function reactiveLoad<T> (
const fetched = await fetch() const fetched = await fetch()
if (data.value != null) { if (data.value != null) {
data.value = Object.assign(data.value, fetched) data.value = Object.assign(data.value, fetched)
} else { }
else {
data.value = fetched data.value = fetched
} }
} catch (e) { }
// eslint-disable-next-line no-console catch (e) {
console.error(e) console.error(e)
data.value = undefined data.value = undefined
} finally { }
finally {
loading.value = false loading.value = false
} }
} }
if (process.client) { if (import.meta.client) {
task() task()
} else { }
else {
await task() await task()
} }
} }
return reactive({ return reactive({
loading, loading,
data data,
}) })
} }
<script setup lang="ts"> <script setup lang="ts">
import { feedsInfo } from '~~/utils/api'
const route = useRoute() const route = useRoute()
const host = process.server const host = import.meta.server
? useRequestHeaders().host ? useRequestHeaders().host
: window.location.host : window.location.host
useHead({ useHead({
link: [ link: [
// We use route.path since we don't use query parameters // We use route.path since we don't use query parameters
{ rel: 'canonical', href: `https://${host}${route.path}` } { rel: 'canonical', href: `https://${host}${route.path}` },
] ],
}) })
</script> </script>
...@@ -32,7 +34,7 @@ useHead({ ...@@ -32,7 +34,7 @@ useHead({
v-for="(list, key) in feedsInfo" v-for="(list, key) in feedsInfo"
:key="key" :key="key"
:to="`/${key}`" :to="`/${key}`"
:class="{ active: $route.path.startsWith(`/${key}`)}" :class="{ active: $route.path.startsWith(`/${key}`) }"
> >
{{ list.title }} {{ list.title }}
</NuxtLink> </NuxtLink>
...@@ -58,16 +60,16 @@ body { ...@@ -58,16 +60,16 @@ body {
background-color: #F4F4F5; background-color: #F4F4F5;
margin: 0; margin: 0;
padding: 0; padding: 0;
color: #18181B; color: #020420;
overflow-y: scroll; overflow-y: scroll;
} }
a { a {
color: #18181B; color: #020420;
text-decoration: none; text-decoration: none;
} }
.header { .header {
background-color: #18181B; background-color: #020420;
z-index: 999; z-index: 999;
height: 55px; height: 55px;
......
import { validFeeds } from '~~/utils/api'
export default defineNuxtRouteMiddleware((from) => { export default defineNuxtRouteMiddleware((from) => {
if (!from.params.feed || !validFeeds.includes(from.params.feed as string)) { if (!from.params.feed || !validFeeds.includes(from.params.feed as string)) {
return navigateTo(`/${validFeeds[0]}/1`) return navigateTo(`/${validFeeds[0]}/1`)
......
<script setup lang="ts"> <script setup lang="ts">
import { feedsInfo } from '~~/utils/api'
definePageMeta({ definePageMeta({
middleware: 'feed' middleware: 'feed',
}) })
const route = useRoute() const route = useRoute()
...@@ -14,7 +16,7 @@ const pageNo = computed(() => Number(page.value) || 1) ...@@ -14,7 +16,7 @@ const pageNo = computed(() => Number(page.value) || 1)
const displayedPage = ref(pageNo.value) const displayedPage = ref(pageNo.value)
useHead({ useHead({
title: feedsInfo[feed.value]?.title title: feedsInfo[feed.value]?.title,
}) })
const state = useStore() const state = useStore()
...@@ -29,8 +31,10 @@ const maxPage = computed(() => { ...@@ -29,8 +31,10 @@ const maxPage = computed(() => {
return +(feedsInfo[feed.value]?.pages) || 0 return +(feedsInfo[feed.value]?.pages) || 0
}) })
function pageChanged (to: number) { function pageChanged(to: number) {
if (!isValidFeed.value) { return } if (!isValidFeed.value) {
return
}
if (to <= 0 || to > maxPage.value) { if (to <= 0 || to > maxPage.value) {
router.replace(`/${feed.value}/1`) router.replace(`/${feed.value}/1`)
...@@ -40,7 +44,7 @@ function pageChanged (to: number) { ...@@ -40,7 +44,7 @@ function pageChanged (to: number) {
// Prefetch next page // Prefetch next page
fetchFeed({ fetchFeed({
feed: feed.value, feed: feed.value,
page: page.value + 1 page: page.value + 1,
}).catch(() => {}) }).catch(() => {})
// transition.value = from === -1 // transition.value = from === -1
......
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
middleware: 'feed' middleware: 'feed',
}) })
</script> </script>
......
<script setup lang="ts"> <script setup lang="ts">
import { validFeeds } from '~~/utils/api'
definePageMeta({ definePageMeta({
middleware: (from) => { middleware: (from) => {
if (from.path === '/') { if (from.path === '/') {
return navigateTo(`/${validFeeds[0]}/1`) return navigateTo(`/${validFeeds[0]}/1`)
} }
} },
}) })
</script> </script>
......
...@@ -7,7 +7,7 @@ const { data: item } = toRefs(resultItem) ...@@ -7,7 +7,7 @@ const { data: item } = toRefs(resultItem)
const { data: comments, loading: commentsLoading } = toRefs(resultComments) const { data: comments, loading: commentsLoading } = toRefs(resultComments)
useHead({ useHead({
title: item.value?.title title: item.value?.title,
}) })
</script> </script>
......
...@@ -10,7 +10,7 @@ useHead({ ...@@ -10,7 +10,7 @@ useHead({
? 'Loading' ? 'Loading'
: user.value : user.value
? user.value.id ? user.value.id
: 'User not found' : 'User not found',
}) })
</script> </script>
......
export function host(url: string) {
const host = url.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace('?id=', '/')
const parts = host.split('.').slice(-3)
if (parts[0] === 'www') {
parts.shift()
}
return parts.join('.')
}
export function timeAgo(time: number | Date) {
const between = Date.now() / 1000 - Number(time)
if (between < 3600) {
return pluralize(~~(between / 60), ' minute')
}
else if (between < 86400) {
return pluralize(~~(between / 3600), ' hour')
}
else { return pluralize(~~(between / 86400), ' day') }
}
export function pluralize(time: number, label: string) {
if (time === 1) {
return time + label
}
return `${time + label}s`
}
export function isAbsolute(url: string) {
return /^https?:\/\//.test(url)
}
export function host (url: string) {
const host = url.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace('?id=', '/')
const parts = host.split('.').slice(-3)
if (parts[0] === 'www') { parts.shift() }
return parts.join('.')
}
export function timeAgo (time: number | Date) {
const between = Date.now() / 1000 - Number(time)
if (between < 3600) { return pluralize(~~(between / 60), ' minute') } else if (between < 86400) { return pluralize(~~(between / 3600), ' hour') } else { return pluralize(~~(between / 86400), ' day') }
}
export function pluralize (time: number, label:string) {
if (time === 1) { return time + label }
return `${time + label}s`
}
export function isAbsolute (url: string) {
return /^https?:\/\//.test(url)
}
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt({
rules: {
'vue/no-v-html': 'off',
},
})
[build.environment]
# bypass npm auto install
NPM_FLAGS = "--version"
NODE_VERSION = "16"
[build]
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build"
export default defineNuxtConfig({ export default defineNuxtConfig({
future: { compatibilityVersion: 4 },
// https://nuxt.com/modules
modules: [
'@nuxthub/core',
'@nuxt/eslint',
],
hub: {
cache: true,
},
postcss: { postcss: {
plugins: { plugins: {
'postcss-nesting': {} 'postcss-nesting': {},
} },
}, },
// https://devtools.nuxt.com
devtools: { devtools: {
enabled: true enabled: true,
} },
// https://eslint.nuxt.com
eslint: {
config: {
stylistic: {
quotes: 'single',
},
},
},
}) })
...@@ -9,9 +9,6 @@ ...@@ -9,9 +9,6 @@
"name": "Sebastien Chopin (@Atinux)" "name": "Sebastien Chopin (@Atinux)"
}, },
{ {
"name": "Alexandre Chopin (@alexchopin)"
},
{
"name": "Pooya Parsa (@pi0)" "name": "Pooya Parsa (@pi0)"
}, },
{ {
...@@ -22,15 +19,17 @@ ...@@ -22,15 +19,17 @@
"dev": "nuxi dev", "dev": "nuxi dev",
"build": "nuxi build", "build": "nuxi build",
"start": "nuxi start", "start": "nuxi start",
"lint": "eslint --ext .vue,.js,.ts --ignore-path .gitignore ." "lint": "eslint ."
}, },
"devDependencies": { "devDependencies": {
"@nuxt/devtools": "^1.0.5", "@nuxt/devtools": "^1.3.3",
"@nuxt/eslint-config": "^0.1.1", "@nuxt/eslint": "^0.3.13",
"@types/node": "^18.16.0", "@nuxt/eslint-config": "^0.3.13",
"eslint": "^8.39.0", "@nuxthub/core": "^0.6.17",
"nuxt": "^3.4.2", "@types/node": "^20.14.2",
"postcss-nesting": "^11.2.2", "eslint": "^9.5.0",
"typescript": "^5.0.4" "nuxt": "^3.12.2",
"postcss-nesting": "^12.1.5",
"typescript": "^5.4.5"
} }
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
import { $fetch } from 'ofetch' import { $fetch } from 'ofetch'
import type { feedsInfo } from '~~/utils/api'
import { feedsInfo, validFeeds } from '~/composables/api' import { validFeeds } from '~~/utils/api'
import { baseURL } from '~/server/constants'
const feedUrls: Record<keyof typeof feedsInfo, string> = { const feedUrls: Record<keyof typeof feedsInfo, string> = {
ask: 'askstories', ask: 'askstories',
jobs: 'jobstories', jobs: 'jobstories',
show: 'showstories', show: 'showstories',
newest: 'newstories', newest: 'newstories',
news: 'topstories' news: 'topstories',
} }
async function fetchFeed (feed: keyof typeof feedsInfo, page = '1') { async function fetchFeed(feed: keyof typeof feedsInfo, page = '1') {
const { fetchItem } = await import('./item.get') const { fetchItem } = await import('./item.get')
const entries = Object.values( const entries = Object.values(
await $fetch(`${baseURL}/${feedUrls[feed]}.json`) await $fetch(`${BASE_URL}/${feedUrls[feed]}.json`),
).slice((Number(page) - 1) * 10, Number(page) * 10) as string[] ).slice((Number(page) - 1) * 10, Number(page) * 10) as string[]
return Promise.all(entries.map(id => fetchItem(id))) return Promise.all(entries.map(id => fetchItem(id)))
} }
export default defineEventHandler((event) => { export default defineCachedEventHandler((event) => {
configureSWRHeaders(event)
const { page = '1', feed = 'news' } = getQuery(event) as { page: string, feed: keyof typeof feedsInfo } const { page = '1', feed = 'news' } = getQuery(event) as { page: string, feed: keyof typeof feedsInfo }
if (!validFeeds.includes(feed) || String(Number(page)) !== page) { if (!validFeeds.includes(feed) || String(Number(page)) !== page) {
throw createError({ throw createError({
statusCode: 422, statusCode: 422,
statusMessage: `Must provide one of ${validFeeds.join(', ')} and a valid page number.` statusMessage: `Must provide one of ${validFeeds.join(', ')} and a valid page number.`,
}) })
} }
return fetchFeed(feed, page) return fetchFeed(feed, page)
}, {
name: 'api/hn',
getKey(event) {
const { page = '1', feed = 'news' } = getQuery(event)
return ['feeds', feed, page].join('/')
},
swr: true,
maxAge: 10,
}) })
import { $fetch } from 'ofetch' import { $fetch } from 'ofetch'
import { baseURL } from '~/server/constants' import type { Item } from '~~/types'
import { Item } from '~/types'
export async function fetchItem ( export async function fetchItem(
id: string, id: string,
withComments = false withComments = false,
): Promise<Item> { ): Promise<Item> {
const item = await $fetch(`${baseURL}/item/${id}.json`) const item = await $fetch(`/item/${id}.json`, { baseURL: BASE_URL })
item.kids = item.kids || {} item.kids = item.kids || {}
return { return {
id: item.id, id: item.id,
...@@ -21,29 +20,36 @@ export async function fetchItem ( ...@@ -21,29 +20,36 @@ export async function fetchItem (
comments: withComments comments: withComments
? await Promise.all( ? await Promise.all(
Object.values(item.kids as string[]).map(id => Object.values(item.kids as string[]).map(id =>
fetchItem(id, withComments) fetchItem(id, withComments),
),
) )
) : [],
: []
} }
} }
export default defineEventHandler((event) => { export default defineCachedEventHandler((event) => {
configureSWRHeaders(event)
const { id } = getQuery(event) as { id?: string } const { id } = getQuery(event) as { id?: string }
if (!id) { if (!id) {
throw createError({ throw createError({
statusCode: 422, statusCode: 422,
statusMessage: 'Must provide a item ID.' statusMessage: 'Must provide a item ID.',
}) })
} }
if (Number.isNaN(+id)) { if (Number.isNaN(+id)) {
throw createError({ throw createError({
statusCode: 400, statusCode: 400,
statusMessage: 'Item ID mush a number but got ' + id statusMessage: 'Item ID mush a number but got ' + id,
}) })
} }
return fetchItem(id, true) return fetchItem(id, true)
}, {
name: 'api/hn',
getKey(event) {
const { id } = getQuery(event)
return ['item', id].join('/')
},
swr: true,
maxAge: 10,
}) })
import { $fetch } from 'ofetch' import { $fetch } from 'ofetch'
import { User } from '~/types' import type { User } from '~~/types'
import { baseURL } from '~/server/constants'
async function fetchUser (id: string): Promise<User> { async function fetchUser(id: string): Promise<User> {
const user = await $fetch(`${baseURL}/user/${id}.json`) const user = await $fetch(`/user/${id}.json`, { baseURL: BASE_URL })
return { return {
id: user.id, id: user.id,
karma: user.karma, karma: user.karma,
created_time: user.created, created_time: user.created,
about: user.about about: user.about,
} }
} }
export default defineEventHandler((event) => { export default defineCachedEventHandler((event) => {
configureSWRHeaders(event)
const { id } = getQuery(event) as { id?: string } const { id } = getQuery(event) as { id?: string }
if (!id) { if (!id) {
throw createError({ throw createError({
statusCode: 422, statusCode: 422,
statusMessage: 'Must provide a user ID.' statusMessage: 'Must provide a user ID.',
}) })
} }
return fetchUser(id) return fetchUser(id)
}, {
name: 'api/hn',
getKey(event) {
const { id } = getQuery(event)
return ['user', id].join('/')
},
swr: true,
maxAge: 10,
}) })
export const baseURL = 'https://hacker-news.firebaseio.com/v0'
export default defineEventHandler((event) => {
const query = getQuery(event)
if (typeof query.csr !== 'undefined') {
event.node.req.headers['x-nuxt-no-ssr'] = 'true'
}
})
export const BASE_URL = 'https://hacker-news.firebaseio.com/v0'
import { H3Event } from 'h3'
export function configureSWRHeaders (event: H3Event) {
setHeader(event, 'Cache-Control', 's-maxage=10, stale-while-revalidate')
}
// Shared between app & server
export const feedsInfo = { export const feedsInfo = {
news: { title: 'News', pages: 10 }, news: { title: 'News', pages: 10 },
newest: { title: 'Newest', pages: 12 }, newest: { title: 'Newest', pages: 12 },
ask: { title: 'Ask', pages: 2 }, ask: { title: 'Ask', pages: 2 },
show: { title: 'Show', pages: 2 }, show: { title: 'Show', pages: 2 },
jobs: { title: 'Jobs', pages: 1 } jobs: { title: 'Jobs', pages: 1 },
} }
export const validFeeds = Object.keys(feedsInfo) export const validFeeds = Object.keys(feedsInfo)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment