How to use get parameter on sveltekit when use adapter-static? - get

I get error message this when build.
Cannot access url.searchParams on a page with prerendering enabled
How to load and use get parameter?
svelte.config.js
import adapter from '#sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
const config = {
preprocess: preprocess({
}),
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null,
precompress: false
}),
prerender: {
default: true
},
trailingSlash: 'always'
}
};
export default config;
qna.svelte
...
import {page} from '$app/stores';
const id = $page.url.searchParams.get('id');
...

You cannot use the searchParams and have a prerendered site at the same time. There would be potentially infinite variants of your searchParams, so you would have to prerender an infinte number of pages.
If you want a dynamic site, do not prerender that page. You can mark individual pages for not prerendering
<script context="module">
export const prerender = false;
</script>
Note that now you will need a fallback page and have to make sure to redirect these pages to the index.html otherwise you will get 404s

Or you can use the hash-based approach and still have the statically built page check this out https://gist.github.com/woss/afbc7293cc2a6632db585e388ff64583

Related

How to achieve code splitting + SSR with #loadable/component ? Is webpack needed for the server code?

I'm trying to add code splitting + SSR to my React web app using #loadable/component.
My current situation: This is how I've implemented SSR for robots on my website. Since it's just for robots, I'm not using hydrate. Basically, I send either the index.html with the JS bundle's script tags for a regular user, or I send a fully rendered HTML page for the robots, without the need to hydrate.
My goal: I'd like to use #loadable/component to always return SSR pages from my server, and use hydrate to attach my JS bundle. And also achieve code splitting with that.
Here is how I currently build my app (pseudo code):
1. webpack BUILD FOR entry { app: "src/index.tsx" } AND OUTPUT BUNDLES TO MY /public FOLDER
2. babel TRANSPILE WHOLE `/src` FOLDER AND OUTPUT FILES TO MY /dist_app FOLDER
It's basically 2 builds, one of them is using webpack to bundle, and the other one basically transpiles the files from src to distApp.
And this is what my server does (pseudo code)
1. CHECK IF USER IS ROBOT (FROM THE USER AGENT STRING)
2. IF REGULAR USER
res.send("public/index.html"); // SEND REGULAR index.html WITH JS BUNDLES GENERATED BY WEBPACK
IF ROBOT
const App = require("./dist_app/App"); // THIS IS THE src/App COMPONENT TRANSPILED BY BABEL
const ssrHtml = ReactDOM.renderToString(<App/>);
// ADD <head> <helmet> <styles> ETC
res.send(ssrHtml);
The steps described above works just fine for my initial requirements (ssr just for robots).
But after I added #loadable/component to achieve code splitting + SSR, the set up above does not work anymore.
Because now I have dynamic imports on some of my routes. For example:
const AsyncPage = loadable(() => import("#app/pages/PageContainer"));
So my renderToString(<App/>) call comes back mostly empty, because it does not load those AsyncPages.
Over on the docs for Loadable components: server side rendering they have an example repo on how to achieve this.
But their example is kind of complex and it seems they are using webpack inside the server. I'll post what they do on their server below.
QUESTION
Do I really have to use webpack to bundle my app's server code in order to use #loadable/component for SSR like they are showing in their example? Can't I just use some kind of babel plugin to convert the dynamic imports into regular require calls? So that I'll be able to render it the way I was doing before?
It's weird, that they use webpack-dev-middleware. It's like this example should be used just for development. Does anybody know a repo with a production example of this?
import path from 'path'
import express from 'express'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { ChunkExtractor } from '#loadable/server'
const app = express()
app.use(express.static(path.join(__dirname, '../../public')))
if (process.env.NODE_ENV !== 'production') {
/* eslint-disable global-require, import/no-extraneous-dependencies */
const { default: webpackConfig } = require('../../webpack.config.babel')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack')
/* eslint-enable global-require, import/no-extraneous-dependencies */
const compiler = webpack(webpackConfig)
app.use(
webpackDevMiddleware(compiler, {
logLevel: 'silent',
publicPath: '/dist/web',
writeToDisk(filePath) {
return /dist\/node\//.test(filePath) || /loadable-stats/.test(filePath)
},
}),
)
}
const nodeStats = path.resolve(
__dirname,
'../../public/dist/node/loadable-stats.json',
)
const webStats = path.resolve(
__dirname,
'../../public/dist/web/loadable-stats.json',
)
app.get('*', (req, res) => {
const nodeExtractor = new ChunkExtractor({ statsFile: nodeStats })
const { default: App } = nodeExtractor.requireEntrypoint()
const webExtractor = new ChunkExtractor({ statsFile: webStats })
const jsx = webExtractor.collectChunks(<App />)
const html = renderToString(jsx)
res.set('content-type', 'text/html')
res.send(`
<!DOCTYPE html>
<html>
<head>
${webExtractor.getLinkTags()}
${webExtractor.getStyleTags()}
</head>
<body>
<div id="main">${html}</div>
${webExtractor.getScriptTags()}
</body>
</html>
`)
})
// eslint-disable-next-line no-console
app.listen(9000, () => console.log('Server started http://localhost:9000'))

Angular +Workbox = build ChunkLoadError: Loading chunk # and Refused to execute script because its MIME

I have added Workbox to Angular in first production deploy everything works fine, but after updating a module and rebuilding angular and injecting Workbox then visiting the site i see the service worker updates to the new version and refreshes the page, but now trying to use the updated module I get errors
Refused to execute script from 'https://example.com/8-es2015.203674bf0547eff7ff27.js'
because its MIME type ('text/html') is not executable,
and strict MIME type checking is enabled.
main-es2015.45ba4a512f87eefb1b3a.js:1 ERROR Error: Uncaught (in promise): ChunkLoadError:
Loading chunk 8 failed.(error: https://example.com/8-es2015.203674bf0547eff7ff27.js)
ChunkLoadError: Loading chunk 8 failed......
I looked at the network in chrome and I see that the file 8-es2015.203674bf0547eff7ff27.js is being served from the (disk cache) unlike the rest of the files which get served by (ServiceWorker), its content is the index.html file I don't know where it came from its not even part of the new build ? chrome places it in top frame section under scripts
Whats the reason for this Error, in the angular.json I have "outputHashing": "all", I delete everything and rebuild but still this errors, its until I clear the browser cash remove the ServiceWorker and hard refresh that the error stops happening until I reload page and it returns. Do I need to delete all the cache after every update, I thought Workbox does this automatically.Do I add something like so in the sw.js
self.addEventListener('activate', event => event.waitUntil(
caches.keys().then(cacheNames => cacheNames.forEach(name => caches.delete(name)))
)
);
Am using express, so I have set the maxAge on the sw.js to 0 and even change the public route to static files to a deep route but nothing
app.use('/sw.js', express.static(path.resolve('./public/dist/static/sw.js'), {maxAge: 0}));
app.use('/', express.static(path.resolve('./public/dist/static/'), {maxAge: 86400000}));
tools: angular 8.2.4 - workbox 4.3.1
Update
Removed workbox and the app worked, am guessing its cause of their new package workbox-window or the way am trying to use it. I have placed it in module service that is loaded from app.module then the service is called from a AppComponent ngOnInit. This could be the wrong way of initializing it.
code setup:
import {Workbox} from 'workbox-window';
#Injectable()
export class WorkerService {
supportWorker: boolean;
supportPush: boolean;
constructor(#Inject(WINDOW) private window: any, private loggerService: LoggerService) {
this.supportWorker = ('serviceWorker' in navigator);
this.supportPush = (this.supportWorker && 'PushManager' in window);
}
initWorker() {
if (this.supportWorker && environment.production) {
const wb = new Workbox('sw.js');
if (wb) {
wb.addEventListener('installed', event => {
if (event.isUpdate) {
// output a toast translated message to users
this.loggerService.info('App.webWorkerUpdate', 10000);
setTimeout(() => this.window.location.reload(), 10000);
}
});
wb.addEventListener('activated', event => {
if (!event.isUpdate) {
this.loggerService.success('App.webWorkerInit', 10000);
}
});
wb.register();
}
}
}
}
This the app component, i thought it would be best to add it to main.ts after bootstrapModule.then() but I don't know how inject a service in this method
#Component({
selector: 'app-root',
template: '<route-handler></route-handler>'
})
export class AppComponent implements OnInit {
constructor(private ws: WorkerService) {
}
ngOnInit(): void {
this.ws.initWorker();
}
}
After setting up Workbox in a different way it worked, the problem effected even chrome which failed to clear all cache after each build when testing it, had to use incognito to make sure everything works.
Here is the solution thanks to Ralph Schaer article a must read. His method is not to Cache-Bust the chunks angular build generates, also he globs in all the production scripts of workbox used by the app into the build folder and finally in the index.html he calls on workbox-window to register the service-worker.

How to handle large number of redirects in Node/Vue app?

I am working on migrating an existing app to a new tech stack that uses Node and MongoDB on the backend and Vue on the frontend. I have a fairly large number of pages that will need to be redirected to new URLs (over 50). I know I can do something like this in the frontend:
const appRouter = new Router({
mode: 'history',
routes: [
{ path: '/a', redirect: '/a2' },
{ path: '/b', redirect: '/b2' },
{ path: '/c', redirect: '/c2' },
]
});
However it doesn't strike me as particularly elegant. I could see keeping the redirects in another file and importing them to keep my router file neater, but that seems like just a formatting benefit.
I'm wondering how other people handle a large number of redirects in Vue? Would this be better to do at the server-level with Node?
If boilerplate is the problem, you can use something like:
const router = new VueRouter({
routes: [
{ path: '/([abc])', redirect: to => {
returect to.path + '2'; // to.path will be like '/a'
}}
]
})
Notice that the part inside () is a regex that can be customized.
I have a fairly large number of pages that will need to be redirected to new URLs
When we talk about redirecting a Uniform Resource Locator (URL) in the context of a Single Page Application (SPA) like Vue with Vue Router, hosted by a web server like Node.js, we might mean one of two things:
we've changed the route of a view within our Vue SPA
we've changed the location of our SPA (the resource) from one location to another.
To determine which kind of redirect you need to do, we can examine how the URL will change. URLs are made up of these components:
scheme:[//[user[:password]#]host[:port]][/path][?query][#fragment]
By default, Vue Router uses the #fragment (hash) portion of the URL to change views, so if this changes then we should redirect using Alias or Redirect.
If any other portion of the URL changes, we should have Node.js return an HTTP status code for redirect, like 301 Moved Permanently or 302 Moved Temporarily.
Normally the solution from #acdcjunior is good enough, but sometimes you may prefer hooking beforeRouteUpdate to implement the redirect.
You can check vue-router: dynamic Routing for more details.
Below is one simple sample is from the official document:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
if ( to.match(new RegExp('your_regex_expression'))) {
next('redirect_url')
} else {
// default
next()
}
}
}
Or in main.js by using global guards:
import router from './router'
router.beforeEach((to, from, next) => {
if ( to.match(new RegExp('your_regex_expression'))) {
next('redirect_url')
} else {
// default
next()
}
})

Using grunt server, how can I redirect all requests to root url?

I am building my first Angular.js application and I'm using Yeoman.
Yeoman uses Grunt to allow you to run a node.js connect server with the command 'grunt server'.
I'm running my angular application in html5 mode. According to the angular docs, this requires a modification of the server to redirect all requests to the root of the application (index.html), since angular apps are single page ajax applications.
"Using [html5] mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html)"
The problem that I'm trying to solve is detailed in this question.
How can I modify my grunt server to redirect all page requests to the index.html page?
First, using your command line, navigate to your directory with your gruntfile.
Type this in the CLI:
npm install --save-dev connect-modrewrite
At the top of your grunt file put this:
var modRewrite = require('connect-modrewrite');
Now the next part, you only want to add modRewrite into your connect:
modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']),
Here is a example of what my "connect" looks like inside my Gruntfile.js. You don't need to worry about my lrSnippet and my ssIncludes. The main thing you need is to just get the modRewrite in.
connect: {
options: {
port: 9000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: '0.0.0.0',
},
livereload: {
options: {
middleware: function (connect) {
return [
modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']),
lrSnippet,
ssInclude(yeomanConfig.app),
mountFolder(connect, '.tmp'),
mountFolder(connect, yeomanConfig.app)
];
}
}
},
test: {
options: {
middleware: function (connect) {
return [
mountFolder(connect, '.tmp'),
mountFolder(connect, 'test')
];
}
}
},
dist: {
options: {
middleware: function (connect) {
return [
mountFolder(connect, yeomanConfig.dist)
];
}
}
}
},
FYI Yeoman/Grunt recently changed the default template for new Gruntfiles.
Copying the default middlewares logic worked for me:
middleware: function (connect, options) {
var middlewares = [];
var directory = options.directory || options.base[options.base.length - 1];
// enable Angular's HTML5 mode
middlewares.push(modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']));
if (!Array.isArray(options.base)) {
options.base = [options.base];
}
options.base.forEach(function(base) {
// Serve static files.
middlewares.push(connect.static(base));
});
// Make directory browse-able.
middlewares.push(connect.directory(directory));
return middlewares;
}
UPDATE: As of grunt-contrib-connect 0.9.0, injecting middlewares into the connect server is much easier:
module.exports = function (grunt) {
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
grunt.initConfig({
// The actual grunt server settings
connect: {
livereload: {
options: {
/* Support `$locationProvider.html5Mode(true);`
* Requires grunt 0.9.0 or higher
* Otherwise you will see this error:
* Running "connect:livereload" (connect) task
* Warning: Cannot call method 'push' of undefined Use --force to continue.
*/
middleware: function(connect, options, middlewares) {
var modRewrite = require('connect-modrewrite');
// enable Angular's HTML5 mode
middlewares.unshift(modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']));
return middlewares;
}
}
}
}
});
}
There is a pull request I sent for this problem: https://github.com/yeoman/generator-angular/pull/132, but you need to apply it manually.
To deeply simplify #Zuriel's answer, here's what I found to work on my behalf.
Install connect-modrewrite: npm install connect-modrewrite --save
Include it in your grunt file: var rewrite = require( "connect-modrewrite" );
Modify your connect options to use the rewrite:
connect: {
options: {
middleware: function ( connect, options, middlewares ) {
var rules = [
"!\\.html|\\.js|\\.css|\\.svg|\\.jp(e?)g|\\.png|\\.gif$ /index.html"
];
middlewares.unshift( rewrite( rules ) );
return middlewares;
}
},
server: {
options: {
port: 9000,
base: "path/to/base"
}
}
}
Simplified this as much as possible. Because you have access to the middlewares provided by connect, it's easy to set the rewrite to the first priority response. I know it's been a while since the question has been asked, but this is one of the top results of google searching regarding the problem.
Idea came from source code: https://github.com/gruntjs/grunt-contrib-connect/blob/master/Gruntfile.js#L126-L139
Rules string from: http://danburzo.ro/grunt/chapters/server/
I tried all of these, but had no luck. I am writing an angular2 app, and the solution came from grunt-connect pushstate.
All I did was:
npm install grunt-connect-pushstate --save
and in the grunt file:
var pushState = require('grunt-connect-pushstate/lib/utils').pushState;
middleware: function (connect, options) {
return [
// Rewrite requests to root so they may be handled by router
pushState(),
// Serve static files
connect.static(options.base)
];
}
and it all worked like magic.
https://www.npmjs.com/package/grunt-connect-pushstate

Backbone Router not working with pushState

I want to have every page request redirect to my index.html, and any link (not #urls - /real/urls) clicked in my app to run through router.js so there are essentially no page refreshes - purely ajax. Is there an easy way to do this with Backbone routing and htaccess?
I have it working at the moment if I take away {pushState: true} and format my links like #login. However, when I enable pushState and click on #login, nothing happens. Instead, it is only once I refresh the page that Backbone interprets the #login and follows the route to render loginView.
Here is my router:
// Filename: router.js
define( [ 'views/beta/requestInvite', 'views/beta/login' ],
function(requestInviteView, loginView) {
var AppRouter = Backbone.Router.extend( {
routes : {
// Pages
'login' : 'login',
// Default
'*actions' : 'defaultAction'
},
// Pages
login : function() {
loginView.render();
},
defaultAction : function(actions) {
requestInviteView.render();
}
});
var initialize = function() {
var app_router = new AppRouter;
Backbone.history.start({pushState: true});
};
return {
initialize : initialize
};
});
What I would like to happen is in requestInviteView, when the link to /login is clicked, the url changes to /login and the loginView is rendered.
Thanks for any help!
Changing from hash to pushstate is not that trivial as changing single parameter as one may be led to think. What i do is catch the click event in my view and call app.navigate to trigger the route.
app.navigate("/login", {trigger: true});
http://backbonejs.org/#Router-navigate
Although Anthony's answer will work, using trigger: true is usually not the best course of action. Instead your app should be structured so you can call navigate with the default trigger value left to false.
Derick Bailey talks about the issue on his blog at http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ (paragraph "The “AHA!” Moment Regarding Router.Navigate’s Second Argument")
In addition, an entire chapter explaining routing in more detail (including why you should leave trigger to false) can be dowloaded for free in this pdf book sample: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf (full disclosure: I'm the book author)

Resources